1# This file is dual licensed under the terms of the Apache License, Version
2# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3# for complete details.
4
5from __future__ import annotations
6
7import abc
8import datetime
9import hashlib
10import ipaddress
11import typing
12from collections.abc import Iterable, Iterator
13
14from cryptography import utils
15from cryptography.hazmat.bindings._rust import asn1
16from cryptography.hazmat.bindings._rust import x509 as rust_x509
17from cryptography.hazmat.primitives import constant_time, serialization
18from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
19from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
20from 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, RelativeDistinguishedName
39from cryptography.x509.oid import (
40 CRLEntryExtensionOID,
41 ExtensionOID,
42 ObjectIdentifier,
43 OCSPExtensionOID,
44)
45
46ExtensionTypeVar = typing.TypeVar(
47 "ExtensionTypeVar", bound="ExtensionType", covariant=True
48)
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_method
86
87
88class DuplicateExtension(Exception):
89 def __init__(self, msg: str, oid: ObjectIdentifier) -> None:
90 super().__init__(msg)
91 self.oid = oid
92
93
94class ExtensionNotFound(Exception):
95 def __init__(self, msg: str, oid: ObjectIdentifier) -> None:
96 super().__init__(msg)
97 self.oid = oid
98
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: ObjectIdentifier
118 ) -> Extension[ExtensionType]:
119 for ext in self:
120 if ext.oid == oid:
121 return ext
122
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 ext
138
139 raise ExtensionNotFound(
140 f"No {extclass} extension was found", extclass.oid
141 )
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_NUMBER
151
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_number
157
158 def __eq__(self, other: object) -> bool:
159 if not isinstance(other, CRLNumber):
160 return NotImplemented
161
162 return self.crl_number == other.crl_number
163
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 @property
171 def crl_number(self) -> int:
172 return self._crl_number
173
174 def public_bytes(self) -> bytes:
175 return rust_x509.encode_extension_value(self)
176
177
178class AuthorityKeyIdentifier(ExtensionType):
179 oid = ExtensionOID.AUTHORITY_KEY_IDENTIFIER
180
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 None
189 ):
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_issuer
199 ):
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, int
207 ):
208 raise TypeError("authority_cert_serial_number must be an integer")
209
210 self._key_identifier = key_identifier
211 self._authority_cert_issuer = authority_cert_issuer
212 self._authority_cert_serial_number = authority_cert_serial_number
213
214 # This takes a subset of CertificatePublicKeyTypes because an issuer
215 # cannot have an X25519/X448 key. This introduces some unfortunate
216 # asymmetry that requires typing users to explicitly
217 # narrow their type, but we should make this accurate and not just
218 # convenient.
219 @classmethod
220 def from_issuer_public_key(
221 cls, public_key: CertificateIssuerPublicKeyTypes
222 ) -> 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 @classmethod
231 def from_issuer_subject_key_identifier(
232 cls, ski: SubjectKeyIdentifier
233 ) -> 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 NotImplemented
251
252 return (
253 self.key_identifier == other.key_identifier
254 and self.authority_cert_issuer == other.authority_cert_issuer
255 and self.authority_cert_serial_number
256 == other.authority_cert_serial_number
257 )
258
259 def __hash__(self) -> int:
260 if self.authority_cert_issuer is None:
261 aci = None
262 else:
263 aci = tuple(self.authority_cert_issuer)
264 return hash(
265 (self.key_identifier, aci, self.authority_cert_serial_number)
266 )
267
268 @property
269 def key_identifier(self) -> bytes | None:
270 return self._key_identifier
271
272 @property
273 def authority_cert_issuer(
274 self,
275 ) -> list[GeneralName] | None:
276 return self._authority_cert_issuer
277
278 @property
279 def authority_cert_serial_number(self) -> int | None:
280 return self._authority_cert_serial_number
281
282 def public_bytes(self) -> bytes:
283 return rust_x509.encode_extension_value(self)
284
285
286class SubjectKeyIdentifier(ExtensionType):
287 oid = ExtensionOID.SUBJECT_KEY_IDENTIFIER
288
289 def __init__(self, digest: bytes) -> None:
290 self._digest = digest
291
292 @classmethod
293 def from_public_key(
294 cls, public_key: CertificatePublicKeyTypes
295 ) -> SubjectKeyIdentifier:
296 return cls(_key_identifier_from_public_key(public_key))
297
298 @property
299 def digest(self) -> bytes:
300 return self._digest
301
302 @property
303 def key_identifier(self) -> bytes:
304 return self._digest
305
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 NotImplemented
312
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_ACCESS
324
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 = descriptions
334
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 NotImplemented
343
344 return self._descriptions == other._descriptions
345
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_ACCESS
355
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 = descriptions
365
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 NotImplemented
374
375 return self._descriptions == other._descriptions
376
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: GeneralName
387 ) -> 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_method
395 self._access_location = access_location
396
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 NotImplemented
406
407 return (
408 self.access_method == other.access_method
409 and self.access_location == other.access_location
410 )
411
412 def __hash__(self) -> int:
413 return hash((self.access_method, self.access_location))
414
415 @property
416 def access_method(self) -> ObjectIdentifier:
417 return self._access_method
418
419 @property
420 def access_location(self) -> GeneralName:
421 return self._access_location
422
423
424class BasicConstraints(ExtensionType):
425 oid = ExtensionOID.BASIC_CONSTRAINTS
426
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 < 0
436 ):
437 raise TypeError(
438 "path_length must be a non-negative integer or None"
439 )
440
441 self._ca = ca
442 self._path_length = path_length
443
444 @property
445 def ca(self) -> bool:
446 return self._ca
447
448 @property
449 def path_length(self) -> int | None:
450 return self._path_length
451
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 NotImplemented
460
461 return self.ca == other.ca and self.path_length == other.path_length
462
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_INDICATOR
472
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_number
478
479 @property
480 def crl_number(self) -> int:
481 return self._crl_number
482
483 def __eq__(self, other: object) -> bool:
484 if not isinstance(other, DeltaCRLIndicator):
485 return NotImplemented
486
487 return self.crl_number == other.crl_number
488
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_POINTS
501
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_points
508 ):
509 raise TypeError(
510 "distribution_points must be a list of DistributionPoint "
511 "objects"
512 )
513
514 self._distribution_points = distribution_points
515
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 NotImplemented
526
527 return self._distribution_points == other._distribution_points
528
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_CRL
538
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_points
545 ):
546 raise TypeError(
547 "distribution_points must be a list of DistributionPoint "
548 "objects"
549 )
550
551 self._distribution_points = distribution_points
552
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 NotImplemented
563
564 return self._distribution_points == other._distribution_points
565
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 reasons
620 or ReasonFlags.remove_from_crl in reasons
621 ):
622 raise ValueError(
623 "unspecified and remove_from_crl are not valid reasons in a "
624 "DistributionPoint"
625 )
626
627 self._full_name = full_name
628 self._relative_name = relative_name
629 self._reasons = reasons
630 self._crl_issuer = crl_issuer
631
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 NotImplemented
642
643 return (
644 self.full_name == other.full_name
645 and self.relative_name == other.relative_name
646 and self.reasons == other.reasons
647 and self.crl_issuer == other.crl_issuer
648 )
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 = None
655
656 if self.crl_issuer is not None:
657 crl_issuer: tuple[GeneralName, ...] | None = tuple(self.crl_issuer)
658 else:
659 crl_issuer = None
660
661 return hash((fn, self.relative_name, self.reasons, crl_issuer))
662
663 @property
664 def full_name(self) -> list[GeneralName] | None:
665 return self._full_name
666
667 @property
668 def relative_name(self) -> RelativeDistinguishedName | None:
669 return self._relative_name
670
671 @property
672 def reasons(self) -> frozenset[ReasonFlags] | None:
673 return self._reasons
674
675 @property
676 def crl_issuer(self) -> list[GeneralName] | None:
677 return self._crl_issuer
678
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 with
694# 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 used
736# 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_CONSTRAINTS
755
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, int
763 ):
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, int
771 ):
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_policy
783 self._inhibit_policy_mapping = inhibit_policy_mapping
784
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 NotImplemented
795
796 return (
797 self.require_explicit_policy == other.require_explicit_policy
798 and self.inhibit_policy_mapping == other.inhibit_policy_mapping
799 )
800
801 def __hash__(self) -> int:
802 return hash(
803 (self.require_explicit_policy, self.inhibit_policy_mapping)
804 )
805
806 @property
807 def require_explicit_policy(self) -> int | None:
808 return self._require_explicit_policy
809
810 @property
811 def inhibit_policy_mapping(self) -> int | None:
812 return self._inhibit_policy_mapping
813
814 def public_bytes(self) -> bytes:
815 return rust_x509.encode_extension_value(self)
816
817
818class CertificatePolicies(ExtensionType):
819 oid = ExtensionOID.CERTIFICATE_POLICIES
820
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 = policies
829
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 NotImplemented
838
839 return self._policies == other._policies
840
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_identifier
858
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_qualifiers
863 ):
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_qualifiers
870
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 NotImplemented
880
881 return (
882 self.policy_identifier == other.policy_identifier
883 and self.policy_qualifiers == other.policy_qualifiers
884 )
885
886 def __hash__(self) -> int:
887 if self.policy_qualifiers is not None:
888 pq = tuple(self.policy_qualifiers)
889 else:
890 pq = None
891
892 return hash((self.policy_identifier, pq))
893
894 @property
895 def policy_identifier(self) -> ObjectIdentifier:
896 return self._policy_identifier
897
898 @property
899 def policy_qualifiers(
900 self,
901 ) -> list[str | UserNotice] | None:
902 return self._policy_qualifiers
903
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, NoticeReference
913 ):
914 raise TypeError(
915 "notice_reference must be None or a NoticeReference"
916 )
917
918 self._notice_reference = notice_reference
919 self._explicit_text = explicit_text
920
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 NotImplemented
930
931 return (
932 self.notice_reference == other.notice_reference
933 and self.explicit_text == other.explicit_text
934 )
935
936 def __hash__(self) -> int:
937 return hash((self.notice_reference, self.explicit_text))
938
939 @property
940 def notice_reference(self) -> NoticeReference | None:
941 return self._notice_reference
942
943 @property
944 def explicit_text(self) -> str | None:
945 return self._explicit_text
946
947
948class NoticeReference:
949 def __init__(
950 self,
951 organization: str | None,
952 notice_numbers: Iterable[int],
953 ) -> None:
954 self._organization = organization
955 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_numbers
960
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 NotImplemented
970
971 return (
972 self.organization == other.organization
973 and self.notice_numbers == other.notice_numbers
974 )
975
976 def __hash__(self) -> int:
977 return hash((self.organization, tuple(self.notice_numbers)))
978
979 @property
980 def organization(self) -> str | None:
981 return self._organization
982
983 @property
984 def notice_numbers(self) -> list[int]:
985 return self._notice_numbers
986
987
988class ExtendedKeyUsage(ExtensionType):
989 oid = ExtensionOID.EXTENDED_KEY_USAGE
990
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 = usages
999
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 NotImplemented
1008
1009 return self._usages == other._usages
1010
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_CHECK
1020
1021 def __eq__(self, other: object) -> bool:
1022 if not isinstance(other, OCSPNoCheck):
1023 return NotImplemented
1024
1025 return True
1026
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_POISON
1039
1040 def __eq__(self, other: object) -> bool:
1041 if not isinstance(other, PrecertPoison):
1042 return NotImplemented
1043
1044 return True
1045
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_FEATURE
1058
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) == 0
1064 ):
1065 raise TypeError(
1066 "features must be a list of elements from the TLSFeatureType "
1067 "enum"
1068 )
1069
1070 self._features = features
1071
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 NotImplemented
1080
1081 return self._features == other._features
1082
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 commonly
1092 # called OCSP Must-Staple when present in the TLS Feature extension in an
1093 # X.509 certificate.
1094 status_request = 5
1095 # status_request_v2 is defined in RFC 6961 and allows multiple OCSP
1096 # responses to be provided. It is not currently in use by clients or
1097 # servers.
1098 status_request_v2 = 17
1099
1100
1101_TLS_FEATURE_TYPE_TO_ENUM = {x.value: x for x in TLSFeatureType}
1102
1103
1104class InhibitAnyPolicy(ExtensionType):
1105 oid = ExtensionOID.INHIBIT_ANY_POLICY
1106
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_certs
1115
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 NotImplemented
1122
1123 return self.skip_certs == other.skip_certs
1124
1125 def __hash__(self) -> int:
1126 return hash(self.skip_certs)
1127
1128 @property
1129 def skip_certs(self) -> int:
1130 return self._skip_certs
1131
1132 def public_bytes(self) -> bytes:
1133 return rust_x509.encode_extension_value(self)
1134
1135
1136class KeyUsage(ExtensionType):
1137 oid = ExtensionOID.KEY_USAGE
1138
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_signature
1158 self._content_commitment = content_commitment
1159 self._key_encipherment = key_encipherment
1160 self._data_encipherment = data_encipherment
1161 self._key_agreement = key_agreement
1162 self._key_cert_sign = key_cert_sign
1163 self._crl_sign = crl_sign
1164 self._encipher_only = encipher_only
1165 self._decipher_only = decipher_only
1166
1167 @property
1168 def digital_signature(self) -> bool:
1169 return self._digital_signature
1170
1171 @property
1172 def content_commitment(self) -> bool:
1173 return self._content_commitment
1174
1175 @property
1176 def key_encipherment(self) -> bool:
1177 return self._key_encipherment
1178
1179 @property
1180 def data_encipherment(self) -> bool:
1181 return self._data_encipherment
1182
1183 @property
1184 def key_agreement(self) -> bool:
1185 return self._key_agreement
1186
1187 @property
1188 def key_cert_sign(self) -> bool:
1189 return self._key_cert_sign
1190
1191 @property
1192 def crl_sign(self) -> bool:
1193 return self._crl_sign
1194
1195 @property
1196 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 )
1201 else:
1202 return self._encipher_only
1203
1204 @property
1205 def decipher_only(self) -> bool:
1206 if not self.key_agreement:
1207 raise ValueError(
1208 "decipher_only is undefined unless key_agreement is true"
1209 )
1210 else:
1211 return self._decipher_only
1212
1213 def __repr__(self) -> str:
1214 try:
1215 encipher_only = self.encipher_only
1216 decipher_only = self.decipher_only
1217 except ValueError:
1218 # Users found None confusing because even though encipher/decipher
1219 # have no meaning unless key_agreement is true, to construct an
1220 # instance of the class you still need to pass False.
1221 encipher_only = False
1222 decipher_only = False
1223
1224 return (
1225 f"<KeyUsage(digital_signature={self.digital_signature}, "
1226 f"content_commitment={self.content_commitment}, "
1227 f"key_encipherment={self.key_encipherment}, "
1228 f"data_encipherment={self.data_encipherment}, "
1229 f"key_agreement={self.key_agreement}, "
1230 f"key_cert_sign={self.key_cert_sign}, crl_sign={self.crl_sign}, "
1231 f"encipher_only={encipher_only}, decipher_only={decipher_only})>"
1232 )
1233
1234 def __eq__(self, other: object) -> bool:
1235 if not isinstance(other, KeyUsage):
1236 return NotImplemented
1237
1238 return (
1239 self.digital_signature == other.digital_signature
1240 and self.content_commitment == other.content_commitment
1241 and self.key_encipherment == other.key_encipherment
1242 and self.data_encipherment == other.data_encipherment
1243 and self.key_agreement == other.key_agreement
1244 and self.key_cert_sign == other.key_cert_sign
1245 and self.crl_sign == other.crl_sign
1246 and self._encipher_only == other._encipher_only
1247 and self._decipher_only == other._decipher_only
1248 )
1249
1250 def __hash__(self) -> int:
1251 return hash(
1252 (
1253 self.digital_signature,
1254 self.content_commitment,
1255 self.key_encipherment,
1256 self.data_encipherment,
1257 self.key_agreement,
1258 self.key_cert_sign,
1259 self.crl_sign,
1260 self._encipher_only,
1261 self._decipher_only,
1262 )
1263 )
1264
1265 def public_bytes(self) -> bytes:
1266 return rust_x509.encode_extension_value(self)
1267
1268
1269class PrivateKeyUsagePeriod(ExtensionType):
1270 oid = ExtensionOID.PRIVATE_KEY_USAGE_PERIOD
1271
1272 def __init__(
1273 self,
1274 not_before: datetime.datetime | None,
1275 not_after: datetime.datetime | None,
1276 ) -> None:
1277 if (
1278 not isinstance(not_before, datetime.datetime)
1279 and not_before is not None
1280 ):
1281 raise TypeError("not_before must be a datetime.datetime or None")
1282
1283 if (
1284 not isinstance(not_after, datetime.datetime)
1285 and not_after is not None
1286 ):
1287 raise TypeError("not_after must be a datetime.datetime or None")
1288
1289 if not_before is None and not_after is None:
1290 raise ValueError(
1291 "At least one of not_before and not_after must not be None"
1292 )
1293
1294 if (
1295 not_before is not None
1296 and not_after is not None
1297 and not_before > not_after
1298 ):
1299 raise ValueError("not_before must be before not_after")
1300
1301 self._not_before = not_before
1302 self._not_after = not_after
1303
1304 @property
1305 def not_before(self) -> datetime.datetime | None:
1306 return self._not_before
1307
1308 @property
1309 def not_after(self) -> datetime.datetime | None:
1310 return self._not_after
1311
1312 def __repr__(self) -> str:
1313 return (
1314 f"<PrivateKeyUsagePeriod(not_before={self.not_before}, "
1315 f"not_after={self.not_after})>"
1316 )
1317
1318 def __eq__(self, other: object) -> bool:
1319 if not isinstance(other, PrivateKeyUsagePeriod):
1320 return NotImplemented
1321
1322 return (
1323 self.not_before == other.not_before
1324 and self.not_after == other.not_after
1325 )
1326
1327 def __hash__(self) -> int:
1328 return hash((self.not_before, self.not_after))
1329
1330 def public_bytes(self) -> bytes:
1331 return rust_x509.encode_extension_value(self)
1332
1333
1334class NameConstraints(ExtensionType):
1335 oid = ExtensionOID.NAME_CONSTRAINTS
1336
1337 def __init__(
1338 self,
1339 permitted_subtrees: Iterable[GeneralName] | None,
1340 excluded_subtrees: Iterable[GeneralName] | None,
1341 ) -> None:
1342 if permitted_subtrees is not None:
1343 permitted_subtrees = list(permitted_subtrees)
1344 if not permitted_subtrees:
1345 raise ValueError(
1346 "permitted_subtrees must be a non-empty list or None"
1347 )
1348 if not all(isinstance(x, GeneralName) for x in permitted_subtrees):
1349 raise TypeError(
1350 "permitted_subtrees must be a list of GeneralName objects "
1351 "or None"
1352 )
1353
1354 self._validate_tree(permitted_subtrees)
1355
1356 if excluded_subtrees is not None:
1357 excluded_subtrees = list(excluded_subtrees)
1358 if not excluded_subtrees:
1359 raise ValueError(
1360 "excluded_subtrees must be a non-empty list or None"
1361 )
1362 if not all(isinstance(x, GeneralName) for x in excluded_subtrees):
1363 raise TypeError(
1364 "excluded_subtrees must be a list of GeneralName objects "
1365 "or None"
1366 )
1367
1368 self._validate_tree(excluded_subtrees)
1369
1370 if permitted_subtrees is None and excluded_subtrees is None:
1371 raise ValueError(
1372 "At least one of permitted_subtrees and excluded_subtrees "
1373 "must not be None"
1374 )
1375
1376 self._permitted_subtrees = permitted_subtrees
1377 self._excluded_subtrees = excluded_subtrees
1378
1379 def __eq__(self, other: object) -> bool:
1380 if not isinstance(other, NameConstraints):
1381 return NotImplemented
1382
1383 return (
1384 self.excluded_subtrees == other.excluded_subtrees
1385 and self.permitted_subtrees == other.permitted_subtrees
1386 )
1387
1388 def _validate_tree(self, tree: Iterable[GeneralName]) -> None:
1389 self._validate_ip_name(tree)
1390 self._validate_dns_name(tree)
1391
1392 def _validate_ip_name(self, tree: Iterable[GeneralName]) -> None:
1393 if any(
1394 isinstance(name, IPAddress)
1395 and not isinstance(
1396 name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)
1397 )
1398 for name in tree
1399 ):
1400 raise TypeError(
1401 "IPAddress name constraints must be an IPv4Network or"
1402 " IPv6Network object"
1403 )
1404
1405 def _validate_dns_name(self, tree: Iterable[GeneralName]) -> None:
1406 if any(
1407 isinstance(name, DNSName) and "*" in name.value for name in tree
1408 ):
1409 raise ValueError(
1410 "DNSName name constraints must not contain the '*' wildcard"
1411 " character"
1412 )
1413
1414 def __repr__(self) -> str:
1415 return (
1416 f"<NameConstraints(permitted_subtrees={self.permitted_subtrees}, "
1417 f"excluded_subtrees={self.excluded_subtrees})>"
1418 )
1419
1420 def __hash__(self) -> int:
1421 if self.permitted_subtrees is not None:
1422 ps: tuple[GeneralName, ...] | None = tuple(self.permitted_subtrees)
1423 else:
1424 ps = None
1425
1426 if self.excluded_subtrees is not None:
1427 es: tuple[GeneralName, ...] | None = tuple(self.excluded_subtrees)
1428 else:
1429 es = None
1430
1431 return hash((ps, es))
1432
1433 @property
1434 def permitted_subtrees(
1435 self,
1436 ) -> list[GeneralName] | None:
1437 return self._permitted_subtrees
1438
1439 @property
1440 def excluded_subtrees(
1441 self,
1442 ) -> list[GeneralName] | None:
1443 return self._excluded_subtrees
1444
1445 def public_bytes(self) -> bytes:
1446 return rust_x509.encode_extension_value(self)
1447
1448
1449class Extension(typing.Generic[ExtensionTypeVar]):
1450 def __init__(
1451 self, oid: ObjectIdentifier, critical: bool, value: ExtensionTypeVar
1452 ) -> None:
1453 if not isinstance(oid, ObjectIdentifier):
1454 raise TypeError(
1455 "oid argument must be an ObjectIdentifier instance."
1456 )
1457
1458 if not isinstance(critical, bool):
1459 raise TypeError("critical must be a boolean value")
1460
1461 self._oid = oid
1462 self._critical = critical
1463 self._value = value
1464
1465 @property
1466 def oid(self) -> ObjectIdentifier:
1467 return self._oid
1468
1469 @property
1470 def critical(self) -> bool:
1471 return self._critical
1472
1473 @property
1474 def value(self) -> ExtensionTypeVar:
1475 return self._value
1476
1477 def __repr__(self) -> str:
1478 return (
1479 f"<Extension(oid={self.oid}, critical={self.critical}, "
1480 f"value={self.value})>"
1481 )
1482
1483 def __eq__(self, other: object) -> bool:
1484 if not isinstance(other, Extension):
1485 return NotImplemented
1486
1487 return (
1488 self.oid == other.oid
1489 and self.critical == other.critical
1490 and self.value == other.value
1491 )
1492
1493 def __hash__(self) -> int:
1494 return hash((self.oid, self.critical, self.value))
1495
1496
1497class GeneralNames:
1498 def __init__(self, general_names: Iterable[GeneralName]) -> None:
1499 general_names = list(general_names)
1500 if not all(isinstance(x, GeneralName) for x in general_names):
1501 raise TypeError(
1502 "Every item in the general_names list must be an "
1503 "object conforming to the GeneralName interface"
1504 )
1505
1506 self._general_names = general_names
1507
1508 __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names")
1509
1510 @typing.overload
1511 def get_values_for_type(
1512 self,
1513 type: type[DNSName]
1514 | type[UniformResourceIdentifier]
1515 | type[RFC822Name],
1516 ) -> list[str]: ...
1517
1518 @typing.overload
1519 def get_values_for_type(
1520 self,
1521 type: type[DirectoryName],
1522 ) -> list[Name]: ...
1523
1524 @typing.overload
1525 def get_values_for_type(
1526 self,
1527 type: type[RegisteredID],
1528 ) -> list[ObjectIdentifier]: ...
1529
1530 @typing.overload
1531 def get_values_for_type(
1532 self, type: type[IPAddress]
1533 ) -> list[_IPAddressTypes]: ...
1534
1535 @typing.overload
1536 def get_values_for_type(
1537 self, type: type[OtherName]
1538 ) -> list[OtherName]: ...
1539
1540 def get_values_for_type(
1541 self,
1542 type: type[DNSName]
1543 | type[DirectoryName]
1544 | type[IPAddress]
1545 | type[OtherName]
1546 | type[RFC822Name]
1547 | type[RegisteredID]
1548 | type[UniformResourceIdentifier],
1549 ) -> (
1550 list[_IPAddressTypes]
1551 | list[str]
1552 | list[OtherName]
1553 | list[Name]
1554 | list[ObjectIdentifier]
1555 ):
1556 # Return the value of each GeneralName, except for OtherName instances
1557 # which we return directly because it has two important properties not
1558 # just one value.
1559 objs = (i for i in self if isinstance(i, type))
1560 if type != OtherName:
1561 return [i.value for i in objs]
1562 return list(objs)
1563
1564 def __repr__(self) -> str:
1565 return f"<GeneralNames({self._general_names})>"
1566
1567 def __eq__(self, other: object) -> bool:
1568 if not isinstance(other, GeneralNames):
1569 return NotImplemented
1570
1571 return self._general_names == other._general_names
1572
1573 def __hash__(self) -> int:
1574 return hash(tuple(self._general_names))
1575
1576
1577class SubjectAlternativeName(ExtensionType):
1578 oid = ExtensionOID.SUBJECT_ALTERNATIVE_NAME
1579
1580 def __init__(self, general_names: Iterable[GeneralName]) -> None:
1581 self._general_names = GeneralNames(general_names)
1582
1583 __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names")
1584
1585 @typing.overload
1586 def get_values_for_type(
1587 self,
1588 type: type[DNSName]
1589 | type[UniformResourceIdentifier]
1590 | type[RFC822Name],
1591 ) -> list[str]: ...
1592
1593 @typing.overload
1594 def get_values_for_type(
1595 self,
1596 type: type[DirectoryName],
1597 ) -> list[Name]: ...
1598
1599 @typing.overload
1600 def get_values_for_type(
1601 self,
1602 type: type[RegisteredID],
1603 ) -> list[ObjectIdentifier]: ...
1604
1605 @typing.overload
1606 def get_values_for_type(
1607 self, type: type[IPAddress]
1608 ) -> list[_IPAddressTypes]: ...
1609
1610 @typing.overload
1611 def get_values_for_type(
1612 self, type: type[OtherName]
1613 ) -> list[OtherName]: ...
1614
1615 def get_values_for_type(
1616 self,
1617 type: type[DNSName]
1618 | type[DirectoryName]
1619 | type[IPAddress]
1620 | type[OtherName]
1621 | type[RFC822Name]
1622 | type[RegisteredID]
1623 | type[UniformResourceIdentifier],
1624 ) -> (
1625 list[_IPAddressTypes]
1626 | list[str]
1627 | list[OtherName]
1628 | list[Name]
1629 | list[ObjectIdentifier]
1630 ):
1631 return self._general_names.get_values_for_type(type)
1632
1633 def __repr__(self) -> str:
1634 return f"<SubjectAlternativeName({self._general_names})>"
1635
1636 def __eq__(self, other: object) -> bool:
1637 if not isinstance(other, SubjectAlternativeName):
1638 return NotImplemented
1639
1640 return self._general_names == other._general_names
1641
1642 def __hash__(self) -> int:
1643 return hash(self._general_names)
1644
1645 def public_bytes(self) -> bytes:
1646 return rust_x509.encode_extension_value(self)
1647
1648
1649class IssuerAlternativeName(ExtensionType):
1650 oid = ExtensionOID.ISSUER_ALTERNATIVE_NAME
1651
1652 def __init__(self, general_names: Iterable[GeneralName]) -> None:
1653 self._general_names = GeneralNames(general_names)
1654
1655 __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names")
1656
1657 @typing.overload
1658 def get_values_for_type(
1659 self,
1660 type: type[DNSName]
1661 | type[UniformResourceIdentifier]
1662 | type[RFC822Name],
1663 ) -> list[str]: ...
1664
1665 @typing.overload
1666 def get_values_for_type(
1667 self,
1668 type: type[DirectoryName],
1669 ) -> list[Name]: ...
1670
1671 @typing.overload
1672 def get_values_for_type(
1673 self,
1674 type: type[RegisteredID],
1675 ) -> list[ObjectIdentifier]: ...
1676
1677 @typing.overload
1678 def get_values_for_type(
1679 self, type: type[IPAddress]
1680 ) -> list[_IPAddressTypes]: ...
1681
1682 @typing.overload
1683 def get_values_for_type(
1684 self, type: type[OtherName]
1685 ) -> list[OtherName]: ...
1686
1687 def get_values_for_type(
1688 self,
1689 type: type[DNSName]
1690 | type[DirectoryName]
1691 | type[IPAddress]
1692 | type[OtherName]
1693 | type[RFC822Name]
1694 | type[RegisteredID]
1695 | type[UniformResourceIdentifier],
1696 ) -> (
1697 list[_IPAddressTypes]
1698 | list[str]
1699 | list[OtherName]
1700 | list[Name]
1701 | list[ObjectIdentifier]
1702 ):
1703 return self._general_names.get_values_for_type(type)
1704
1705 def __repr__(self) -> str:
1706 return f"<IssuerAlternativeName({self._general_names})>"
1707
1708 def __eq__(self, other: object) -> bool:
1709 if not isinstance(other, IssuerAlternativeName):
1710 return NotImplemented
1711
1712 return self._general_names == other._general_names
1713
1714 def __hash__(self) -> int:
1715 return hash(self._general_names)
1716
1717 def public_bytes(self) -> bytes:
1718 return rust_x509.encode_extension_value(self)
1719
1720
1721class CertificateIssuer(ExtensionType):
1722 oid = CRLEntryExtensionOID.CERTIFICATE_ISSUER
1723
1724 def __init__(self, general_names: Iterable[GeneralName]) -> None:
1725 self._general_names = GeneralNames(general_names)
1726
1727 __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names")
1728
1729 @typing.overload
1730 def get_values_for_type(
1731 self,
1732 type: type[DNSName]
1733 | type[UniformResourceIdentifier]
1734 | type[RFC822Name],
1735 ) -> list[str]: ...
1736
1737 @typing.overload
1738 def get_values_for_type(
1739 self,
1740 type: type[DirectoryName],
1741 ) -> list[Name]: ...
1742
1743 @typing.overload
1744 def get_values_for_type(
1745 self,
1746 type: type[RegisteredID],
1747 ) -> list[ObjectIdentifier]: ...
1748
1749 @typing.overload
1750 def get_values_for_type(
1751 self, type: type[IPAddress]
1752 ) -> list[_IPAddressTypes]: ...
1753
1754 @typing.overload
1755 def get_values_for_type(
1756 self, type: type[OtherName]
1757 ) -> list[OtherName]: ...
1758
1759 def get_values_for_type(
1760 self,
1761 type: type[DNSName]
1762 | type[DirectoryName]
1763 | type[IPAddress]
1764 | type[OtherName]
1765 | type[RFC822Name]
1766 | type[RegisteredID]
1767 | type[UniformResourceIdentifier],
1768 ) -> (
1769 list[_IPAddressTypes]
1770 | list[str]
1771 | list[OtherName]
1772 | list[Name]
1773 | list[ObjectIdentifier]
1774 ):
1775 return self._general_names.get_values_for_type(type)
1776
1777 def __repr__(self) -> str:
1778 return f"<CertificateIssuer({self._general_names})>"
1779
1780 def __eq__(self, other: object) -> bool:
1781 if not isinstance(other, CertificateIssuer):
1782 return NotImplemented
1783
1784 return self._general_names == other._general_names
1785
1786 def __hash__(self) -> int:
1787 return hash(self._general_names)
1788
1789 def public_bytes(self) -> bytes:
1790 return rust_x509.encode_extension_value(self)
1791
1792
1793class CRLReason(ExtensionType):
1794 oid = CRLEntryExtensionOID.CRL_REASON
1795
1796 def __init__(self, reason: ReasonFlags) -> None:
1797 if not isinstance(reason, ReasonFlags):
1798 raise TypeError("reason must be an element from ReasonFlags")
1799
1800 self._reason = reason
1801
1802 def __repr__(self) -> str:
1803 return f"<CRLReason(reason={self._reason})>"
1804
1805 def __eq__(self, other: object) -> bool:
1806 if not isinstance(other, CRLReason):
1807 return NotImplemented
1808
1809 return self.reason == other.reason
1810
1811 def __hash__(self) -> int:
1812 return hash(self.reason)
1813
1814 @property
1815 def reason(self) -> ReasonFlags:
1816 return self._reason
1817
1818 def public_bytes(self) -> bytes:
1819 return rust_x509.encode_extension_value(self)
1820
1821
1822class InvalidityDate(ExtensionType):
1823 oid = CRLEntryExtensionOID.INVALIDITY_DATE
1824
1825 def __init__(self, invalidity_date: datetime.datetime) -> None:
1826 if not isinstance(invalidity_date, datetime.datetime):
1827 raise TypeError("invalidity_date must be a datetime.datetime")
1828
1829 self._invalidity_date = invalidity_date
1830
1831 def __repr__(self) -> str:
1832 return f"<InvalidityDate(invalidity_date={self._invalidity_date})>"
1833
1834 def __eq__(self, other: object) -> bool:
1835 if not isinstance(other, InvalidityDate):
1836 return NotImplemented
1837
1838 return self.invalidity_date == other.invalidity_date
1839
1840 def __hash__(self) -> int:
1841 return hash(self.invalidity_date)
1842
1843 @property
1844 def invalidity_date(self) -> datetime.datetime:
1845 return self._invalidity_date
1846
1847 @property
1848 def invalidity_date_utc(self) -> datetime.datetime:
1849 if self._invalidity_date.tzinfo is None:
1850 return self._invalidity_date.replace(tzinfo=datetime.timezone.utc)
1851 else:
1852 return self._invalidity_date.astimezone(tz=datetime.timezone.utc)
1853
1854 def public_bytes(self) -> bytes:
1855 return rust_x509.encode_extension_value(self)
1856
1857
1858class PrecertificateSignedCertificateTimestamps(ExtensionType):
1859 oid = ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS
1860
1861 def __init__(
1862 self,
1863 signed_certificate_timestamps: Iterable[SignedCertificateTimestamp],
1864 ) -> None:
1865 signed_certificate_timestamps = list(signed_certificate_timestamps)
1866 if not all(
1867 isinstance(sct, SignedCertificateTimestamp)
1868 for sct in signed_certificate_timestamps
1869 ):
1870 raise TypeError(
1871 "Every item in the signed_certificate_timestamps list must be "
1872 "a SignedCertificateTimestamp"
1873 )
1874 self._signed_certificate_timestamps = signed_certificate_timestamps
1875
1876 __len__, __iter__, __getitem__ = _make_sequence_methods(
1877 "_signed_certificate_timestamps"
1878 )
1879
1880 def __repr__(self) -> str:
1881 return f"<PrecertificateSignedCertificateTimestamps({list(self)})>"
1882
1883 def __hash__(self) -> int:
1884 return hash(tuple(self._signed_certificate_timestamps))
1885
1886 def __eq__(self, other: object) -> bool:
1887 if not isinstance(other, PrecertificateSignedCertificateTimestamps):
1888 return NotImplemented
1889
1890 return (
1891 self._signed_certificate_timestamps
1892 == other._signed_certificate_timestamps
1893 )
1894
1895 def public_bytes(self) -> bytes:
1896 return rust_x509.encode_extension_value(self)
1897
1898
1899class SignedCertificateTimestamps(ExtensionType):
1900 oid = ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS
1901
1902 def __init__(
1903 self,
1904 signed_certificate_timestamps: Iterable[SignedCertificateTimestamp],
1905 ) -> None:
1906 signed_certificate_timestamps = list(signed_certificate_timestamps)
1907 if not all(
1908 isinstance(sct, SignedCertificateTimestamp)
1909 for sct in signed_certificate_timestamps
1910 ):
1911 raise TypeError(
1912 "Every item in the signed_certificate_timestamps list must be "
1913 "a SignedCertificateTimestamp"
1914 )
1915 self._signed_certificate_timestamps = signed_certificate_timestamps
1916
1917 __len__, __iter__, __getitem__ = _make_sequence_methods(
1918 "_signed_certificate_timestamps"
1919 )
1920
1921 def __repr__(self) -> str:
1922 return f"<SignedCertificateTimestamps({list(self)})>"
1923
1924 def __hash__(self) -> int:
1925 return hash(tuple(self._signed_certificate_timestamps))
1926
1927 def __eq__(self, other: object) -> bool:
1928 if not isinstance(other, SignedCertificateTimestamps):
1929 return NotImplemented
1930
1931 return (
1932 self._signed_certificate_timestamps
1933 == other._signed_certificate_timestamps
1934 )
1935
1936 def public_bytes(self) -> bytes:
1937 return rust_x509.encode_extension_value(self)
1938
1939
1940class OCSPNonce(ExtensionType):
1941 oid = OCSPExtensionOID.NONCE
1942
1943 def __init__(self, nonce: bytes) -> None:
1944 if not isinstance(nonce, bytes):
1945 raise TypeError("nonce must be bytes")
1946
1947 self._nonce = nonce
1948
1949 def __eq__(self, other: object) -> bool:
1950 if not isinstance(other, OCSPNonce):
1951 return NotImplemented
1952
1953 return self.nonce == other.nonce
1954
1955 def __hash__(self) -> int:
1956 return hash(self.nonce)
1957
1958 def __repr__(self) -> str:
1959 return f"<OCSPNonce(nonce={self.nonce!r})>"
1960
1961 @property
1962 def nonce(self) -> bytes:
1963 return self._nonce
1964
1965 def public_bytes(self) -> bytes:
1966 return rust_x509.encode_extension_value(self)
1967
1968
1969class OCSPAcceptableResponses(ExtensionType):
1970 oid = OCSPExtensionOID.ACCEPTABLE_RESPONSES
1971
1972 def __init__(self, responses: Iterable[ObjectIdentifier]) -> None:
1973 responses = list(responses)
1974 if any(not isinstance(r, ObjectIdentifier) for r in responses):
1975 raise TypeError("All responses must be ObjectIdentifiers")
1976
1977 self._responses = responses
1978
1979 def __eq__(self, other: object) -> bool:
1980 if not isinstance(other, OCSPAcceptableResponses):
1981 return NotImplemented
1982
1983 return self._responses == other._responses
1984
1985 def __hash__(self) -> int:
1986 return hash(tuple(self._responses))
1987
1988 def __repr__(self) -> str:
1989 return f"<OCSPAcceptableResponses(responses={self._responses})>"
1990
1991 def __iter__(self) -> Iterator[ObjectIdentifier]:
1992 return iter(self._responses)
1993
1994 def public_bytes(self) -> bytes:
1995 return rust_x509.encode_extension_value(self)
1996
1997
1998class IssuingDistributionPoint(ExtensionType):
1999 oid = ExtensionOID.ISSUING_DISTRIBUTION_POINT
2000
2001 def __init__(
2002 self,
2003 full_name: Iterable[GeneralName] | None,
2004 relative_name: RelativeDistinguishedName | None,
2005 only_contains_user_certs: bool,
2006 only_contains_ca_certs: bool,
2007 only_some_reasons: frozenset[ReasonFlags] | None,
2008 indirect_crl: bool,
2009 only_contains_attribute_certs: bool,
2010 ) -> None:
2011 if full_name is not None:
2012 full_name = list(full_name)
2013
2014 if only_some_reasons and (
2015 not isinstance(only_some_reasons, frozenset)
2016 or not all(isinstance(x, ReasonFlags) for x in only_some_reasons)
2017 ):
2018 raise TypeError(
2019 "only_some_reasons must be None or frozenset of ReasonFlags"
2020 )
2021
2022 if only_some_reasons and (
2023 ReasonFlags.unspecified in only_some_reasons
2024 or ReasonFlags.remove_from_crl in only_some_reasons
2025 ):
2026 raise ValueError(
2027 "unspecified and remove_from_crl are not valid reasons in an "
2028 "IssuingDistributionPoint"
2029 )
2030
2031 if not (
2032 isinstance(only_contains_user_certs, bool)
2033 and isinstance(only_contains_ca_certs, bool)
2034 and isinstance(indirect_crl, bool)
2035 and isinstance(only_contains_attribute_certs, bool)
2036 ):
2037 raise TypeError(
2038 "only_contains_user_certs, only_contains_ca_certs, "
2039 "indirect_crl and only_contains_attribute_certs "
2040 "must all be boolean."
2041 )
2042
2043 # Per RFC5280 Section 5.2.5, the Issuing Distribution Point extension
2044 # in a CRL can have only one of onlyContainsUserCerts,
2045 # onlyContainsCACerts, onlyContainsAttributeCerts set to TRUE.
2046 crl_constraints = [
2047 only_contains_user_certs,
2048 only_contains_ca_certs,
2049 only_contains_attribute_certs,
2050 ]
2051
2052 if len([x for x in crl_constraints if x]) > 1:
2053 raise ValueError(
2054 "Only one of the following can be set to True: "
2055 "only_contains_user_certs, only_contains_ca_certs, "
2056 "only_contains_attribute_certs"
2057 )
2058
2059 if not any(
2060 [
2061 only_contains_user_certs,
2062 only_contains_ca_certs,
2063 indirect_crl,
2064 only_contains_attribute_certs,
2065 full_name,
2066 relative_name,
2067 only_some_reasons,
2068 ]
2069 ):
2070 raise ValueError(
2071 "Cannot create empty extension: "
2072 "if only_contains_user_certs, only_contains_ca_certs, "
2073 "indirect_crl, and only_contains_attribute_certs are all False"
2074 ", then either full_name, relative_name, or only_some_reasons "
2075 "must have a value."
2076 )
2077
2078 self._only_contains_user_certs = only_contains_user_certs
2079 self._only_contains_ca_certs = only_contains_ca_certs
2080 self._indirect_crl = indirect_crl
2081 self._only_contains_attribute_certs = only_contains_attribute_certs
2082 self._only_some_reasons = only_some_reasons
2083 self._full_name = full_name
2084 self._relative_name = relative_name
2085
2086 def __repr__(self) -> str:
2087 return (
2088 f"<IssuingDistributionPoint(full_name={self.full_name}, "
2089 f"relative_name={self.relative_name}, "
2090 f"only_contains_user_certs={self.only_contains_user_certs}, "
2091 f"only_contains_ca_certs={self.only_contains_ca_certs}, "
2092 f"only_some_reasons={self.only_some_reasons}, "
2093 f"indirect_crl={self.indirect_crl}, "
2094 "only_contains_attribute_certs="
2095 f"{self.only_contains_attribute_certs})>"
2096 )
2097
2098 def __eq__(self, other: object) -> bool:
2099 if not isinstance(other, IssuingDistributionPoint):
2100 return NotImplemented
2101
2102 return (
2103 self.full_name == other.full_name
2104 and self.relative_name == other.relative_name
2105 and self.only_contains_user_certs == other.only_contains_user_certs
2106 and self.only_contains_ca_certs == other.only_contains_ca_certs
2107 and self.only_some_reasons == other.only_some_reasons
2108 and self.indirect_crl == other.indirect_crl
2109 and self.only_contains_attribute_certs
2110 == other.only_contains_attribute_certs
2111 )
2112
2113 def __hash__(self) -> int:
2114 return hash(
2115 (
2116 self.full_name,
2117 self.relative_name,
2118 self.only_contains_user_certs,
2119 self.only_contains_ca_certs,
2120 self.only_some_reasons,
2121 self.indirect_crl,
2122 self.only_contains_attribute_certs,
2123 )
2124 )
2125
2126 @property
2127 def full_name(self) -> list[GeneralName] | None:
2128 return self._full_name
2129
2130 @property
2131 def relative_name(self) -> RelativeDistinguishedName | None:
2132 return self._relative_name
2133
2134 @property
2135 def only_contains_user_certs(self) -> bool:
2136 return self._only_contains_user_certs
2137
2138 @property
2139 def only_contains_ca_certs(self) -> bool:
2140 return self._only_contains_ca_certs
2141
2142 @property
2143 def only_some_reasons(
2144 self,
2145 ) -> frozenset[ReasonFlags] | None:
2146 return self._only_some_reasons
2147
2148 @property
2149 def indirect_crl(self) -> bool:
2150 return self._indirect_crl
2151
2152 @property
2153 def only_contains_attribute_certs(self) -> bool:
2154 return self._only_contains_attribute_certs
2155
2156 def public_bytes(self) -> bytes:
2157 return rust_x509.encode_extension_value(self)
2158
2159
2160class MSCertificateTemplate(ExtensionType):
2161 oid = ExtensionOID.MS_CERTIFICATE_TEMPLATE
2162
2163 def __init__(
2164 self,
2165 template_id: ObjectIdentifier,
2166 major_version: int | None,
2167 minor_version: int | None,
2168 ) -> None:
2169 if not isinstance(template_id, ObjectIdentifier):
2170 raise TypeError("oid must be an ObjectIdentifier")
2171 self._template_id = template_id
2172 if (
2173 major_version is not None and not isinstance(major_version, int)
2174 ) or (
2175 minor_version is not None and not isinstance(minor_version, int)
2176 ):
2177 raise TypeError(
2178 "major_version and minor_version must be integers or None"
2179 )
2180 self._major_version = major_version
2181 self._minor_version = minor_version
2182
2183 @property
2184 def template_id(self) -> ObjectIdentifier:
2185 return self._template_id
2186
2187 @property
2188 def major_version(self) -> int | None:
2189 return self._major_version
2190
2191 @property
2192 def minor_version(self) -> int | None:
2193 return self._minor_version
2194
2195 def __repr__(self) -> str:
2196 return (
2197 f"<MSCertificateTemplate(template_id={self.template_id}, "
2198 f"major_version={self.major_version}, "
2199 f"minor_version={self.minor_version})>"
2200 )
2201
2202 def __eq__(self, other: object) -> bool:
2203 if not isinstance(other, MSCertificateTemplate):
2204 return NotImplemented
2205
2206 return (
2207 self.template_id == other.template_id
2208 and self.major_version == other.major_version
2209 and self.minor_version == other.minor_version
2210 )
2211
2212 def __hash__(self) -> int:
2213 return hash((self.template_id, self.major_version, self.minor_version))
2214
2215 def public_bytes(self) -> bytes:
2216 return rust_x509.encode_extension_value(self)
2217
2218
2219class NamingAuthority:
2220 def __init__(
2221 self,
2222 id: ObjectIdentifier | None,
2223 url: str | None,
2224 text: str | None,
2225 ) -> None:
2226 if id is not None and not isinstance(id, ObjectIdentifier):
2227 raise TypeError("id must be an ObjectIdentifier")
2228
2229 if url is not None and not isinstance(url, str):
2230 raise TypeError("url must be a str")
2231
2232 if text is not None and not isinstance(text, str):
2233 raise TypeError("text must be a str")
2234
2235 self._id = id
2236 self._url = url
2237 self._text = text
2238
2239 @property
2240 def id(self) -> ObjectIdentifier | None:
2241 return self._id
2242
2243 @property
2244 def url(self) -> str | None:
2245 return self._url
2246
2247 @property
2248 def text(self) -> str | None:
2249 return self._text
2250
2251 def __repr__(self) -> str:
2252 return (
2253 f"<NamingAuthority("
2254 f"id={self.id}, url={self.url}, text={self.text})>"
2255 )
2256
2257 def __eq__(self, other: object) -> bool:
2258 if not isinstance(other, NamingAuthority):
2259 return NotImplemented
2260
2261 return (
2262 self.id == other.id
2263 and self.url == other.url
2264 and self.text == other.text
2265 )
2266
2267 def __hash__(self) -> int:
2268 return hash(
2269 (
2270 self.id,
2271 self.url,
2272 self.text,
2273 )
2274 )
2275
2276
2277class ProfessionInfo:
2278 def __init__(
2279 self,
2280 naming_authority: NamingAuthority | None,
2281 profession_items: Iterable[str],
2282 profession_oids: Iterable[ObjectIdentifier] | None,
2283 registration_number: str | None,
2284 add_profession_info: bytes | None,
2285 ) -> None:
2286 if naming_authority is not None and not isinstance(
2287 naming_authority, NamingAuthority
2288 ):
2289 raise TypeError("naming_authority must be a NamingAuthority")
2290
2291 profession_items = list(profession_items)
2292 if not all(isinstance(item, str) for item in profession_items):
2293 raise TypeError(
2294 "Every item in the profession_items list must be a str"
2295 )
2296
2297 if profession_oids is not None:
2298 profession_oids = list(profession_oids)
2299 if not all(
2300 isinstance(oid, ObjectIdentifier) for oid in profession_oids
2301 ):
2302 raise TypeError(
2303 "Every item in the profession_oids list must be an "
2304 "ObjectIdentifier"
2305 )
2306
2307 if registration_number is not None and not isinstance(
2308 registration_number, str
2309 ):
2310 raise TypeError("registration_number must be a str")
2311
2312 if add_profession_info is not None and not isinstance(
2313 add_profession_info, bytes
2314 ):
2315 raise TypeError("add_profession_info must be bytes")
2316
2317 self._naming_authority = naming_authority
2318 self._profession_items = profession_items
2319 self._profession_oids = profession_oids
2320 self._registration_number = registration_number
2321 self._add_profession_info = add_profession_info
2322
2323 @property
2324 def naming_authority(self) -> NamingAuthority | None:
2325 return self._naming_authority
2326
2327 @property
2328 def profession_items(self) -> list[str]:
2329 return self._profession_items
2330
2331 @property
2332 def profession_oids(self) -> list[ObjectIdentifier] | None:
2333 return self._profession_oids
2334
2335 @property
2336 def registration_number(self) -> str | None:
2337 return self._registration_number
2338
2339 @property
2340 def add_profession_info(self) -> bytes | None:
2341 return self._add_profession_info
2342
2343 def __repr__(self) -> str:
2344 return (
2345 f"<ProfessionInfo(naming_authority={self.naming_authority}, "
2346 f"profession_items={self.profession_items}, "
2347 f"profession_oids={self.profession_oids}, "
2348 f"registration_number={self.registration_number}, "
2349 f"add_profession_info={self.add_profession_info!r})>"
2350 )
2351
2352 def __eq__(self, other: object) -> bool:
2353 if not isinstance(other, ProfessionInfo):
2354 return NotImplemented
2355
2356 return (
2357 self.naming_authority == other.naming_authority
2358 and self.profession_items == other.profession_items
2359 and self.profession_oids == other.profession_oids
2360 and self.registration_number == other.registration_number
2361 and self.add_profession_info == other.add_profession_info
2362 )
2363
2364 def __hash__(self) -> int:
2365 if self.profession_oids is not None:
2366 profession_oids = tuple(self.profession_oids)
2367 else:
2368 profession_oids = None
2369 return hash(
2370 (
2371 self.naming_authority,
2372 tuple(self.profession_items),
2373 profession_oids,
2374 self.registration_number,
2375 self.add_profession_info,
2376 )
2377 )
2378
2379
2380class Admission:
2381 def __init__(
2382 self,
2383 admission_authority: GeneralName | None,
2384 naming_authority: NamingAuthority | None,
2385 profession_infos: Iterable[ProfessionInfo],
2386 ) -> None:
2387 if admission_authority is not None and not isinstance(
2388 admission_authority, GeneralName
2389 ):
2390 raise TypeError("admission_authority must be a GeneralName")
2391
2392 if naming_authority is not None and not isinstance(
2393 naming_authority, NamingAuthority
2394 ):
2395 raise TypeError("naming_authority must be a NamingAuthority")
2396
2397 profession_infos = list(profession_infos)
2398 if not all(
2399 isinstance(info, ProfessionInfo) for info in profession_infos
2400 ):
2401 raise TypeError(
2402 "Every item in the profession_infos list must be a "
2403 "ProfessionInfo"
2404 )
2405
2406 self._admission_authority = admission_authority
2407 self._naming_authority = naming_authority
2408 self._profession_infos = profession_infos
2409
2410 @property
2411 def admission_authority(self) -> GeneralName | None:
2412 return self._admission_authority
2413
2414 @property
2415 def naming_authority(self) -> NamingAuthority | None:
2416 return self._naming_authority
2417
2418 @property
2419 def profession_infos(self) -> list[ProfessionInfo]:
2420 return self._profession_infos
2421
2422 def __repr__(self) -> str:
2423 return (
2424 f"<Admission(admission_authority={self.admission_authority}, "
2425 f"naming_authority={self.naming_authority}, "
2426 f"profession_infos={self.profession_infos})>"
2427 )
2428
2429 def __eq__(self, other: object) -> bool:
2430 if not isinstance(other, Admission):
2431 return NotImplemented
2432
2433 return (
2434 self.admission_authority == other.admission_authority
2435 and self.naming_authority == other.naming_authority
2436 and self.profession_infos == other.profession_infos
2437 )
2438
2439 def __hash__(self) -> int:
2440 return hash(
2441 (
2442 self.admission_authority,
2443 self.naming_authority,
2444 tuple(self.profession_infos),
2445 )
2446 )
2447
2448
2449class Admissions(ExtensionType):
2450 oid = ExtensionOID.ADMISSIONS
2451
2452 def __init__(
2453 self,
2454 authority: GeneralName | None,
2455 admissions: Iterable[Admission],
2456 ) -> None:
2457 if authority is not None and not isinstance(authority, GeneralName):
2458 raise TypeError("authority must be a GeneralName")
2459
2460 admissions = list(admissions)
2461 if not all(
2462 isinstance(admission, Admission) for admission in admissions
2463 ):
2464 raise TypeError(
2465 "Every item in the contents_of_admissions list must be an "
2466 "Admission"
2467 )
2468
2469 self._authority = authority
2470 self._admissions = admissions
2471
2472 __len__, __iter__, __getitem__ = _make_sequence_methods("_admissions")
2473
2474 @property
2475 def authority(self) -> GeneralName | None:
2476 return self._authority
2477
2478 def __repr__(self) -> str:
2479 return (
2480 f"<Admissions(authority={self._authority}, "
2481 f"admissions={self._admissions})>"
2482 )
2483
2484 def __eq__(self, other: object) -> bool:
2485 if not isinstance(other, Admissions):
2486 return NotImplemented
2487
2488 return (
2489 self.authority == other.authority
2490 and self._admissions == other._admissions
2491 )
2492
2493 def __hash__(self) -> int:
2494 return hash((self.authority, tuple(self._admissions)))
2495
2496 def public_bytes(self) -> bytes:
2497 return rust_x509.encode_extension_value(self)
2498
2499
2500class UnrecognizedExtension(ExtensionType):
2501 def __init__(self, oid: ObjectIdentifier, value: bytes) -> None:
2502 if not isinstance(oid, ObjectIdentifier):
2503 raise TypeError("oid must be an ObjectIdentifier")
2504 self._oid = oid
2505 self._value = value
2506
2507 @property
2508 def oid(self) -> ObjectIdentifier: # type: ignore[override]
2509 return self._oid
2510
2511 @property
2512 def value(self) -> bytes:
2513 return self._value
2514
2515 def __repr__(self) -> str:
2516 return f"<UnrecognizedExtension(oid={self.oid}, value={self.value!r})>"
2517
2518 def __eq__(self, other: object) -> bool:
2519 if not isinstance(other, UnrecognizedExtension):
2520 return NotImplemented
2521
2522 return self.oid == other.oid and self.value == other.value
2523
2524 def __hash__(self) -> int:
2525 return hash((self.oid, self.value))
2526
2527 def public_bytes(self) -> bytes:
2528 return self.value