Coverage for pki_tools/types/extensions.py: 96%
665 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-11-02 22:00 +0000
« prev ^ index » next coverage.py v7.14.3, created at 2026-11-02 22:00 +0000
1import importlib
2import typing
3from collections.abc import Iterator
4from enum import Enum
6from cryptography import x509
7from cryptography.hazmat._oid import (
8 AuthorityInformationAccessOID,
9 ExtensionOID,
10)
11from cryptography.hazmat.bindings._rust import ObjectIdentifier
12from cryptography.x509.extensions import (
13 ExtensionNotFound,
14 ExtensionTypeVar,
15)
16from loguru import logger
17from pydantic import ConfigDict, Field
19from pki_tools.types.crypto_parser import CryptoParser
20from pki_tools.types.name import Name
21from pki_tools.types.utils import _byte_to_hex, _hex_to_byte
23GENERAL_NAME_MODULE = importlib.import_module("cryptography.x509.general_name")
24EXTENSIONS_MODULE = importlib.import_module("cryptography.x509.extensions")
27class Extension(CryptoParser):
28 """
29 Represents a cryptographic extension.
31 Attributes:
32 critical: Indicates whether the extension is critical.
33 """
35 critical: bool | None = False
37 @property
38 def name(self) -> str:
39 """
40 Get the name of the extension.
42 Returns:
43 str: The name of the extension.
44 """
45 name = "".join(
46 [" " + c if c.isupper() else c for c in self.__class__.__name__]
47 )[1:]
48 if self.critical:
49 name += " (critical)"
50 return name
53class GeneralName(CryptoParser):
54 """
55 Represents a general name.
57 Attributes:
58 name: The name of the general name.
59 value: The value of the general name.
60 """
62 name: str
63 value: str | Name
65 @classmethod
66 def from_cryptography(
67 cls: type["GeneralName"], crypto_obj: x509.GeneralName
68 ) -> "GeneralName":
69 """
70 Create a GeneralName instance from a cryptography GeneralName object.
72 Args:
73 crypto_obj: The cryptography GeneralName object.
75 Returns:
76 The GeneralName instance.
77 """
78 if isinstance(crypto_obj, x509.DNSName):
79 return DnsName(crypto_obj.value)
80 elif isinstance(crypto_obj, x509.IPAddress):
81 return IpAddress(str(crypto_obj.value))
82 elif isinstance(crypto_obj, x509.DirectoryName):
83 return DirectoryName.from_cryptography(crypto_obj)
84 elif isinstance(crypto_obj, x509.OtherName):
85 return OtherName.from_cryptography(crypto_obj)
86 elif isinstance(crypto_obj, x509.RegisteredID):
87 return RegisteredId.from_cryptography(crypto_obj)
89 return globals()[crypto_obj.__class__.__name__](crypto_obj.value)
91 def _to_cryptography(self) -> x509.GeneralName:
92 return getattr(GENERAL_NAME_MODULE, self.name)(self.value)
94 def _string_dict(self) -> dict[str, str]:
95 val = self.value
96 if isinstance(self.value, Name):
97 val = str(self.value)
99 return {
100 "name": self.name,
101 "value": val,
102 }
105class DnsName(GeneralName):
106 """
107 Represents a DNS name.
109 Attributes:
110 value: The DNS name value.
111 """
113 def __init__(self, value: str):
114 super().__init__(name="DNSName", value=value)
117class DirectoryName(GeneralName):
118 """
119 Represents a directory name.
121 Attributes:
122 value: The directory name value.
123 """
125 def __init__(self, value: Name):
126 super().__init__(name="DirectoryName", value=value)
128 @classmethod
129 def from_cryptography(
130 cls: type["DirectoryName"], crypto_obj: x509.GeneralName
131 ) -> "DirectoryName":
132 """
133 Create a DirectoryName instance from a cryptography DirectoryName
134 object.
136 Args:
137 crypto_obj: The cryptography DirectoryName object.
139 Returns:
140 The DirectoryName instance.
141 """
142 return cls(value=Name.from_cryptography(crypto_obj.value))
144 def _to_cryptography(self) -> x509.GeneralName:
145 return x509.DirectoryName(self.value._to_cryptography())
148class IpAddress(GeneralName):
149 """
150 Represents an IP address. Can either be a IPv4/IPv6 single address or
151 network.
153 Attributes:
154 value: The IP address or network
155 """
157 def __init__(self, value: str):
158 super().__init__(name="IPAddress", value=value)
160 def _to_cryptography(self) -> x509.GeneralName:
161 cls_name = "IPv4"
162 if ":" in self.value:
163 cls_name = "IPv6"
165 if "/" in self.value:
166 cls_name += "Network"
167 else:
168 cls_name += "Address"
170 module = importlib.import_module("ipaddress")
171 value = getattr(module, cls_name)(self.value)
172 return x509.IPAddress(value)
175class OtherName(GeneralName):
176 """
177 Represents an other name.
179 Attributes:
180 value: The other name value.
181 oid: The object identifier (OID) of the other name.
182 """
184 oid: str
186 def __init__(self, value: str, oid: str):
187 super().__init__(name="OtherName", value=value, oid=oid)
189 @classmethod
190 def from_cryptography(
191 cls: type["OtherName"], crypto_obj: x509.OtherName
192 ) -> "OtherName":
193 """
194 Create a OtherName instance from a cryptography OtherName object.
196 Args:
197 crypto_obj: The cryptography OtherName object.
199 Returns:
200 The OtherName instance.
201 """
202 return cls(
203 value=_byte_to_hex(crypto_obj.value),
204 oid=crypto_obj.type_id.dotted_string,
205 )
207 def _to_cryptography(self) -> x509.OtherName:
208 return x509.OtherName(
209 type_id=x509.ObjectIdentifier(self.oid),
210 value=_hex_to_byte(self.value),
211 )
214class RFC822Name(GeneralName):
215 """
216 Represents an RFC822 name.
218 Attributes:
219 value: The RFC822 name value.
220 """
222 def __init__(self, value: str):
223 super().__init__(name="RFC822Name", value=value)
226class RegisteredId(GeneralName):
227 """
228 Represents a registered ID.
230 Attributes:
231 value: The registered ID value.
232 """
234 def __init__(self, value: str):
235 super().__init__(name="RegisteredID", value=value)
237 @classmethod
238 def from_cryptography(
239 cls: type["RegisteredId"], crypto_obj: x509.RegisteredID
240 ) -> "RegisteredId":
241 """
242 Create a RegisteredId instance from a cryptography RegisteredId object.
244 Args:
245 crypto_obj: The cryptography RegisteredId object.
247 Returns:
248 The RegisteredId instance.
249 """
250 return cls(value=str(crypto_obj.value.dotted_string))
252 def _to_cryptography(self) -> x509.RegisteredID:
253 return x509.RegisteredID(x509.ObjectIdentifier(self.value))
256class UniformResourceIdentifier(GeneralName):
257 """
258 Represents a uniform resource identifier (URI).
260 Attributes:
261 value: The URI value.
262 """
264 def __init__(self, value: str):
265 super().__init__(name="UniformResourceIdentifier", value=value)
268class AuthorityKeyIdentifier(Extension):
269 """
270 Represents an authority key identifier extension.
272 Attributes:
273 key_identifier: The key identifier.
274 authority_cert_issuer: The issuer of the authority certificate.
275 authority_cert_serial_number: The serial number of the authority
276 certificate.
277 """
279 key_identifier: bytes | None
280 authority_cert_issuer: list[GeneralName] | None
281 authority_cert_serial_number: int | None
283 @classmethod
284 def from_cryptography(
285 cls: type["AuthorityKeyIdentifier"],
286 extension: x509.AuthorityKeyIdentifier,
287 ) -> "AuthorityKeyIdentifier":
288 """
289 Create an AuthorityKeyIdentifier instance from a cryptography
290 AuthorityKeyIdentifier object.
292 Args:
293 extension: The cryptography AuthorityKeyIdentifier object.
295 Returns:
296 The AuthorityKeyIdentifier instance.
297 """
298 issuers = None
299 if extension.authority_cert_issuer is not None:
300 issuers = []
301 for general_name in extension.authority_cert_issuer:
302 issuers.append(GeneralName.from_cryptography(general_name))
304 return cls(
305 key_identifier=extension.key_identifier,
306 authority_cert_issuer=issuers,
307 authority_cert_serial_number=extension.authority_cert_serial_number,
308 _x509_obj=extension,
309 )
311 def _string_dict(self):
312 ret = {}
314 if self.key_identifier is not None:
315 hex_key = _byte_to_hex(self.key_identifier)
316 ret["Key Identifier"] = hex_key
317 if self.authority_cert_issuer is not None:
318 authority_cert_issuer = []
319 for general_name in self.authority_cert_issuer:
320 authority_cert_issuer.append(general_name._string_dict())
321 ret["Authority Cert Issuer"] = authority_cert_issuer
322 if self.authority_cert_serial_number is not None:
323 ret["Authority Cert Serial Number"] = (
324 self.authority_cert_serial_number
325 )
327 if ret:
328 return {self.name: ret}
329 return {}
331 def _to_cryptography(self) -> x509.AuthorityKeyIdentifier:
332 authority_cert_issuer = []
333 for general_name in self.authority_cert_issuer:
334 authority = getattr(GENERAL_NAME_MODULE, general_name.name)(
335 general_name.value
336 )
337 authority_cert_issuer.append(authority)
339 return x509.AuthorityKeyIdentifier(
340 key_identifier=self.key_identifier,
341 authority_cert_issuer=authority_cert_issuer,
342 authority_cert_serial_number=self.authority_cert_serial_number,
343 )
346class SubjectKeyIdentifier(Extension):
347 """
348 Represents a subject key identifier extension.
350 Attributes:
351 subject_key_identifier: The subject key identifier.
352 """
354 subject_key_identifier: bytes
356 @classmethod
357 def from_cryptography(
358 cls: type["SubjectKeyIdentifier"], extension: x509.SubjectKeyIdentifier
359 ) -> "SubjectKeyIdentifier":
360 """
361 Create a SubjectKeyIdentifier instance from a cryptography
362 SubjectKeyIdentifier object.
364 Args:
365 extension: The cryptography SubjectKeyIdentifier object.
367 Returns:
368 The SubjectKeyIdentifier instance.
369 """
370 return cls(
371 subject_key_identifier=extension.key_identifier,
372 _x509_obj=extension,
373 )
375 def _string_dict(self):
376 hex_key = _byte_to_hex(self.subject_key_identifier)
377 return {self.name: {"Subject Key Identifier": hex_key}}
379 def _to_cryptography(self) -> x509.SubjectKeyIdentifier:
380 return x509.SubjectKeyIdentifier(self.subject_key_identifier)
383class KeyUsage(Extension):
384 """
385 Represents a key usage extension.
387 Attributes:
388 digital_signature: Indicates if digital signature is allowed.
389 content_commitment: Indicates if content commitment is allowed.
390 key_encipherment: Indicates if key encipherment is allowed.
391 data_encipherment: Indicates if data encipherment is allowed.
392 key_agreement: Indicates if key agreement is allowed.
393 key_cert_sign: Indicates if key certificate signing is allowed.
394 crl_sign: Indicates if CRL signing is allowed.
395 encipher_only: Indicates if encipher only is allowed.
396 decipher_only: Indicates if decipher only is allowed.
397 """
399 digital_signature: bool = False
400 content_commitment: bool = False
401 key_encipherment: bool = False
402 data_encipherment: bool = False
403 key_agreement: bool = False
404 key_cert_sign: bool = False
405 crl_sign: bool = False
406 encipher_only: bool = False
407 decipher_only: bool = False
409 @classmethod
410 def from_cryptography(
411 cls: type["KeyUsage"], extension: x509.KeyUsage
412 ) -> "KeyUsage":
413 """
414 Create a KeyUsage instance from a cryptography KeyUsage object.
416 Args:
417 extension: The cryptography KeyUsage object.
419 Returns:
420 The KeyUsage instance.
421 """
422 try:
423 encipher_only = extension.encipher_only
424 except ValueError:
425 encipher_only = False
426 try:
427 decipher_only = extension.decipher_only
428 except ValueError:
429 decipher_only = False
431 return cls(
432 digital_signature=extension.digital_signature,
433 content_commitment=extension.content_commitment,
434 key_encipherment=extension.key_encipherment,
435 data_encipherment=extension.data_encipherment,
436 key_agreement=extension.key_agreement,
437 key_cert_sign=extension.key_cert_sign,
438 crl_sign=extension.crl_sign,
439 encipher_only=encipher_only,
440 decipher_only=decipher_only,
441 _x509_obj=extension,
442 )
444 def _string_dict(self):
445 true_fields = []
446 for field in KeyUsage.model_fields:
447 if field == "critical":
448 continue
450 field_title = " ".join(ele.title() for ele in field.split("_"))
451 if getattr(self, field):
452 true_fields.append(field_title)
454 return {self.name: ", ".join(true_fields)}
456 def _to_cryptography(self) -> x509.KeyUsage:
457 return x509.KeyUsage(
458 digital_signature=self.digital_signature,
459 content_commitment=self.content_commitment,
460 key_encipherment=self.key_encipherment,
461 data_encipherment=self.data_encipherment,
462 key_agreement=self.key_agreement,
463 key_cert_sign=self.key_cert_sign,
464 crl_sign=self.crl_sign,
465 encipher_only=self.encipher_only,
466 decipher_only=self.decipher_only,
467 )
470class NoticeReference(Extension):
471 """
472 Represents a notice reference extension.
474 Attributes:
475 organization: The organization associated with the notice.
476 notice_numbers: List of notice numbers.
477 """
479 organization: str
480 notice_numbers: list[int]
482 @classmethod
483 def from_cryptography(cls, notice_reference: x509.NoticeReference):
484 return cls(
485 organization=notice_reference.organization,
486 notice_numbers=notice_reference.notice_numbers,
487 _x509_obj=notice_reference,
488 )
490 def _string_dict(self):
491 return {
492 self.name: {
493 "Organization": self.organization,
494 "Notice Numbers": self.notice_numbers,
495 }
496 }
498 def _to_cryptography(self) -> x509.NoticeReference:
499 return x509.NoticeReference(
500 organization=self.organization,
501 notice_numbers=self.notice_numbers,
502 )
505class UserNotice(Extension):
506 """
507 Represents a user notice extension.
509 Attributes:
510 notice_reference: The notice reference associated with the user notice.
511 explicit_text: The explicit text of the user notice.
512 """
514 notice_reference: NoticeReference | None
515 explicit_text: str | None
517 @classmethod
518 def from_cryptography(
519 cls: type["UserNotice"], policy_info: x509.UserNotice
520 ) -> "UserNotice":
521 """
522 Create a UserNotice instance from a cryptography UserNotice object.
524 Args:
525 policy_info: The cryptography UserNotice object.
527 Returns:
528 The UserNotice instance.
529 """
530 return cls(
531 notice_reference=NoticeReference.from_cryptography(
532 policy_info.notice_reference
533 ),
534 explicit_text=policy_info.explicit_text,
535 _x509_obj=policy_info,
536 )
538 def _string_dict(self):
539 ret = {self.name: {}}
541 if self.notice_reference is not None:
542 ret[self.name].update(self.notice_reference._string_dict())
543 if self.explicit_text is not None:
544 ret[self.name]["Explicit Text"] = self.explicit_text
545 return ret
547 def _to_cryptography(self) -> x509.UserNotice:
548 return x509.UserNotice(
549 notice_reference=self.notice_reference._to_cryptography(),
550 explicit_text=self.explicit_text,
551 )
554class PolicyInformation(Extension):
555 """
556 Represents a policy information extension.
558 Attributes:
559 policy_identifier: The policy identifier associated with the policy
560 information.
561 policy_qualifiers: List of policy qualifiers.
562 """
564 policy_identifier: str
565 policy_qualifiers: list[str | UserNotice] | None
567 @classmethod
568 def from_cryptography(
569 cls: type["PolicyInformation"], policy_info: x509.PolicyInformation
570 ) -> "PolicyInformation":
571 """
572 Create a PolicyInformation instance from a cryptography
573 PolicyInformation object.
575 Args:
576 policy_info: The cryptography PolicyInformation object.
578 Returns:
579 The PolicyInformation instance.
580 """
581 policy_qualifiers = None
582 if policy_info.policy_qualifiers is not None:
583 policy_qualifiers = []
584 for qualifier in policy_info.policy_qualifiers:
585 if isinstance(qualifier, str):
586 policy_qualifiers.append(qualifier)
587 else:
588 policy_qualifiers.append(
589 UserNotice.from_cryptography(qualifier)
590 )
592 return cls(
593 policy_identifier=policy_info.policy_identifier.dotted_string,
594 policy_qualifiers=policy_qualifiers,
595 _x509_obj=policy_info,
596 )
598 def _string_dict(self):
599 name = f"Policy {self.policy_identifier}"
600 ret = {name: []}
602 if self.policy_qualifiers is not None:
603 for qualifier in self.policy_qualifiers:
604 if isinstance(qualifier, str):
605 ret[name].append(f"CPS: {qualifier}")
606 else:
607 ret[name].append(qualifier._string_dict())
608 return ret
610 def _to_cryptography(self) -> x509.PolicyInformation:
611 qualifiers = []
612 for qualifier in self.policy_qualifiers:
613 if isinstance(qualifier, str):
614 qualifiers.append(qualifier)
615 else:
616 qualifiers.append(qualifier._to_cryptography())
618 return x509.PolicyInformation(
619 policy_identifier=x509.ObjectIdentifier(self.policy_identifier),
620 policy_qualifiers=qualifiers,
621 )
624class CertificatePolicies(Extension):
625 """
626 Represents a certificate policies extension.
628 Attributes:
629 policy_information: List of policy information.
630 """
632 policy_information: list[PolicyInformation]
634 @classmethod
635 def from_cryptography(
636 cls: type["CertificatePolicies"], extension: x509.CertificatePolicies
637 ) -> "CertificatePolicies":
638 """
639 Create a CertificatePolicies instance from a cryptography
640 CertificatePolicies object.
642 Args:
643 extension: The cryptography CertificatePolicies object.
645 Returns:
646 The CertificatePolicies instance.
647 """
648 res = []
649 for policy_information in extension:
650 info = PolicyInformation.from_cryptography(policy_information)
651 res.append(info)
653 return cls(
654 policy_information=res,
655 _x509_obj=extension,
656 )
658 def _string_dict(self):
659 ret = {self.name: []}
660 for policy_information in self.policy_information:
661 ret[self.name].append(policy_information._string_dict())
663 return ret
665 def _to_cryptography(self) -> x509.CertificatePolicies:
666 policies = []
667 for policy in self.policy_information:
668 policies.append(policy._to_cryptography())
670 return x509.CertificatePolicies(policies)
673class AlternativeName(Extension):
674 """
675 Represents an alternative name extension.
677 Attributes:
678 general_names: List of general names.
679 """
681 general_names: list[GeneralName]
683 @classmethod
684 def from_cryptography(
685 cls: type["AlternativeName"],
686 extension: x509.SubjectAlternativeName | x509.IssuerAlternativeName,
687 ) -> "AlternativeName":
688 """
689 Create an AlternativeName instance from a cryptography
690 SubjectAlternativeName or IssuerAlternativeName object.
692 Args:
693 extension: The cryptography SubjectAlternativeName or
694 IssuerAlternativeName object.
696 Returns:
697 The AlternativeName instance.
698 """
699 names = []
700 for general_name in extension:
701 names.append(GeneralName.from_cryptography(general_name))
703 return cls(
704 general_names=names,
705 _x509_obj=extension,
706 )
708 def _string_dict(self):
709 general_names = []
710 for general_name in self.general_names:
711 general_names.append(general_name._string_dict())
712 return {self.name: general_names}
714 def _to_cryptography(
715 self,
716 ) -> x509.SubjectAlternativeName | x509.IssuerAlternativeName:
717 general_names = []
718 for general_name in self.general_names:
719 general_names.append(general_name._to_cryptography())
721 subclass_name = type(self).__name__
723 return getattr(EXTENSIONS_MODULE, subclass_name)(general_names)
726class SubjectAlternativeName(AlternativeName):
727 """
728 Represents a subject alternative name extension.
729 """
732class IssuerAlternativeName(AlternativeName):
733 """
734 Represents an issuer alternative name extension.
735 """
738class SubjectDirectoryAttributes(Extension):
739 """
740 Represents a subject directory attributes extension.
742 Attributes:
743 attributes: List of attributes.
744 """
746 attributes: list[str]
748 @classmethod
749 def from_cryptography(
750 cls: type["SubjectDirectoryAttributes"],
751 extension: x509.UnrecognizedExtension,
752 ) -> "SubjectDirectoryAttributes":
753 """
754 Create a SubjectDirectoryAttributes instance from a cryptography
755 UnrecognizedExtension object.
757 Args:
758 extension: The cryptography UnrecognizedExtension object.
760 Returns:
761 The SubjectDirectoryAttributes instance.
762 """
763 attributes = []
764 vals = extension.value
765 if not isinstance(vals, list):
766 vals = [vals]
768 for val in vals:
769 if isinstance(val, bytes):
770 val = _byte_to_hex(val)
772 attributes.append(val)
774 return cls(attributes=attributes, _x509_obj=extension)
776 def _to_cryptography(self) -> x509.UnrecognizedExtension:
777 values = []
778 for attribute in self.attributes:
779 try:
780 attribute = _hex_to_byte(attribute)
781 except ValueError:
782 pass
784 values.append(attribute)
786 if len(values) == 1:
787 values = values[0].encode()
788 else:
789 values = bytes(values)
790 return x509.UnrecognizedExtension(
791 oid=ObjectIdentifier("2.5.29.9"), value=values
792 )
794 def _string_dict(self):
795 return {self.name: self.attributes}
798class BasicConstraints(Extension):
799 """
800 Represents a basic constraints extension.
802 Attributes:
803 ca: Indicates if the certificate is a CA.
804 path_len_constrain: The path length constraint.
805 """
807 ca: bool
808 path_len_constraint: int | None = None
810 @classmethod
811 def from_cryptography(
812 cls: type["BasicConstraints"], extension: x509.BasicConstraints
813 ) -> "BasicConstraints":
814 """
815 Create a BasicConstraints instance from a cryptography
816 BasicConstraints object.
818 Args:
819 extension: The cryptography BasicConstraints object.
821 Returns:
822 The BasicConstraints instance.
823 """
824 return cls(
825 ca=extension.ca,
826 path_len_constraint=extension.path_length,
827 _x509_obj=extension,
828 )
830 def _string_dict(self):
831 ret = {
832 self.name: {
833 "CA": self.ca,
834 }
835 }
837 if self.path_len_constraint is not None:
838 ret["Path Lenght"] = self.path_len_constraint
840 return ret
842 def _to_cryptography(self) -> x509.BasicConstraints:
843 return x509.BasicConstraints(
844 ca=self.ca,
845 path_length=self.path_len_constraint,
846 )
849class NameConstraints(Extension):
850 """
851 Represents a name constraints extension.
853 Attributes:
854 permitted_subtrees: List of permitted subtrees.
855 excluded_subtrees: List of excluded subtrees.
856 """
858 permitted_subtrees: list[GeneralName] | None
859 excluded_subtrees: list[GeneralName] | None
861 @classmethod
862 def from_cryptography(cls, extension: x509.NameConstraints):
863 permitted_subtrees = []
864 for permitted in extension.permitted_subtrees:
865 permitted_subtrees.append(GeneralName.from_cryptography(permitted))
866 if not permitted_subtrees:
867 permitted_subtrees = None
869 excluded_subtrees = []
870 for excluded in extension.excluded_subtrees:
871 excluded_subtrees.append(GeneralName.from_cryptography(excluded))
872 if not excluded_subtrees:
873 excluded_subtrees = None
875 return cls(
876 permitted_subtrees=permitted_subtrees,
877 excluded_subtrees=excluded_subtrees,
878 _x509_obj=extension,
879 )
881 def _string_dict(self):
882 permitted_subtrees = []
883 for permitted_subtree in self.permitted_subtrees:
884 permitted_subtrees.append(permitted_subtree._string_dict())
886 excluded_subtrees = []
887 for excluded_subtree in self.excluded_subtrees:
888 excluded_subtrees.append(excluded_subtree._string_dict())
890 return {
891 self.name: {
892 "Permitted Subtrees": permitted_subtrees,
893 "Excluded Subtrees": excluded_subtrees,
894 }
895 }
897 def _to_cryptography(self) -> x509.NameConstraints:
898 permitted_subtrees = None
899 if self.permitted_subtrees is not None:
900 permitted_subtrees = []
901 for permitted_subtree in self.permitted_subtrees:
902 permitted_subtrees.append(permitted_subtree._to_cryptography())
904 excluded_subtrees = None
905 if self.excluded_subtrees is not None:
906 excluded_subtrees = []
907 for excluded_subtree in self.excluded_subtrees:
908 excluded_subtrees.append(excluded_subtree._to_cryptography())
910 return x509.NameConstraints(
911 permitted_subtrees=permitted_subtrees,
912 excluded_subtrees=excluded_subtrees,
913 )
916class PolicyConstraints(Extension):
917 """
918 Represents a policy constraints extension.
920 Attributes:
921 require_explicit_policy: The require explicit policy value.
922 inhibit_policy_mapping: The inhibit policy mapping value.
923 """
925 require_explicit_policy: int | None
926 inhibit_policy_mapping: int | None
928 @classmethod
929 def from_cryptography(cls, extension: x509.PolicyConstraints):
930 return cls(
931 require_explicit_policy=extension.require_explicit_policy,
932 inhibit_policy_mapping=extension.inhibit_policy_mapping,
933 _x509_obj=extension,
934 )
936 def _string_dict(self):
937 ret = {self.name: {}}
939 if self.require_explicit_policy is not None:
940 ret[self.name]["Require Explicit Policy"] = (
941 self.require_explicit_policy
942 )
943 if self.inhibit_policy_mapping is not None:
944 ret[self.name]["Inhibit Policy Mapping"] = (
945 self.inhibit_policy_mapping
946 )
948 return ret
950 def _to_cryptography(self) -> x509.PolicyConstraints:
951 return x509.PolicyConstraints(
952 require_explicit_policy=self.require_explicit_policy,
953 inhibit_policy_mapping=self.inhibit_policy_mapping,
954 )
957EKU_OID_MAPPING = {
958 "1.3.6.1.5.5.7.3.1": "Server Authentication",
959 "1.3.6.1.5.5.7.3.2": "Client Authentication",
960 "1.3.6.1.5.5.7.3.3": "Code Signing",
961 "1.3.6.1.5.5.7.3.4": "Email Protection",
962 "1.3.6.1.5.5.7.3.8": "Time Stamping",
963 "1.3.6.1.5.5.7.3.9": "OCSP Signing",
964 "1.3.6.1.5.5.7.3.17": "IPsec IKE",
965 "2.5.29.37.0": "Any Extended Key Usage",
966 "1.3.6.1.4.1.311.20.2.2": "Smart Card Logon",
967 "1.3.6.1.5.2.3.5": "Kerberos PKINIT KDC",
968 "1.3.6.1.4.1.11129.2.4.4": "Certificate Transparency",
969}
972class ExtendedKeyUsage(Extension):
973 """
974 Represents the Extended Key Usage extension in X.509 certificates.
976 Attributes:
977 ext_key_usage_syntax: List of extended key usage OIDs.
978 """
980 ext_key_usage_syntax: list[str]
982 @classmethod
983 def from_cryptography(
984 cls: type["ExtendedKeyUsage"], extension: x509.ExtendedKeyUsage
985 ) -> "ExtendedKeyUsage":
986 """
987 Constructs an ExtendedKeyUsage object from a cryptography
988 ExtendedKeyUsage object.
990 Args:
991 extension: The cryptography ExtendedKeyUsage object.
993 Returns:
994 The constructed ExtendedKeyUsage object.
995 """
996 ext_key_usage_syntax = []
997 for key_usage in extension:
998 ext_key_usage_syntax.append(key_usage.dotted_string)
999 return cls(
1000 ext_key_usage_syntax=ext_key_usage_syntax,
1001 _x509_obj=extension,
1002 )
1004 def _string_dict(self):
1005 names = []
1006 for oid in self.ext_key_usage_syntax:
1007 names.append(
1008 EKU_OID_MAPPING.get(
1009 oid,
1010 f"Unknown OID ({oid})",
1011 )
1012 )
1014 return {self.name: names}
1016 def _to_cryptography(self) -> x509.ExtendedKeyUsage:
1017 oids = []
1018 for oid in self.ext_key_usage_syntax:
1019 oids.append(x509.ObjectIdentifier(oid))
1020 return x509.ExtendedKeyUsage(oids)
1023class AttributeTypeAndValue(CryptoParser):
1024 """
1025 Represents an Attribute Type and Value in X.509 certificates.
1027 Attributes:
1028 oid: The Object Identifier (OID) of the attribute.
1029 value: The value of the attribute.
1030 """
1032 oid: str
1033 value: str
1035 @classmethod
1036 def from_cryptography(
1037 cls: type["AttributeTypeAndValue"], x509_obj: x509.NameAttribute
1038 ) -> "AttributeTypeAndValue":
1039 """
1040 Constructs an AttributeTypeAndValue object from a cryptography
1041 NameAttribute object.
1043 Args:
1044 x509_obj: The cryptography NameAttribute object.
1046 Returns:
1047 The constructed AttributeTypeAndValue object.
1048 """
1049 return cls(
1050 oid=x509_obj.oid.dotted_string,
1051 value=x509_obj.value,
1052 _x509_obj=x509_obj,
1053 )
1055 def _to_cryptography(self) -> x509.NameAttribute:
1056 return x509.NameAttribute(
1057 oid=x509.ObjectIdentifier(self.oid),
1058 value=self.value,
1059 )
1061 def _string_dict(self) -> dict[str, str]:
1062 return {
1063 "OID": self.oid,
1064 "Value": self.value,
1065 }
1068class RelativeDistinguishedName(CryptoParser):
1069 """
1070 Represents a Relative Distinguished Name in X.509 certificates.
1072 Attributes:
1073 attributes: List of attributes in the RDN.
1074 """
1076 attributes: list[AttributeTypeAndValue]
1078 def __iter__(self) -> Iterator:
1079 return iter(self.attributes)
1081 @classmethod
1082 def from_cryptography(
1083 cls: type["RelativeDistinguishedName"],
1084 x509_obj: x509.RelativeDistinguishedName,
1085 ) -> "RelativeDistinguishedName":
1086 """
1087 Constructs a RelativeDistinguishedName object from a cryptography
1088 RelativeDistinguishedName object.
1090 Args:
1091 x509_obj: The cryptography RelativeDistinguishedName object.
1093 Returns:
1094 The constructed RelativeDistinguishedName object.
1095 """
1096 attributes = []
1097 for name_attribute in x509_obj:
1098 attributes.append(
1099 AttributeTypeAndValue.from_cryptography(name_attribute)
1100 )
1102 cls(attributes=attributes, _x509_obj=x509_obj)
1104 def _string_dict(self) -> dict:
1105 attributes = []
1106 for att in self.attributes:
1107 attributes.append(att._string_dict())
1109 return {"RelativeDistinguishedName": attributes}
1111 def _to_cryptography(self) -> x509.RelativeDistinguishedName:
1112 name_attributes = []
1113 for att in self.attributes:
1114 name_attributes.append(att._to_cryptography())
1115 return x509.RelativeDistinguishedName(name_attributes)
1118class Reason(Enum):
1119 """
1120 Reasons for the DistributionPoint extension
1121 """
1123 unspecified = "unspecified"
1124 key_compromise = "key_compromise"
1125 ca_compromise = "ca_compromise"
1126 affiliation_changed = "affiliation_changed"
1127 superseded = "superseded"
1128 cessation_of_operation = "cessation_of_operation"
1129 certificate_hold = "certificate_hold"
1130 privilege_withdrawn = "privilege_withdrawn"
1131 aa_compromise = "aa_compromise"
1132 remove_from_crl = "remove_from_crl"
1135class DistributionPoint(CryptoParser):
1136 """
1137 Represents a Distribution Point in X.509 certificates.
1139 Attributes:
1140 full_name: List of full names associated with the distribution point.
1141 name_relative_to_crl_issuer: Relative name to the CRL issuer.
1142 reasons: List of reasons for the distribution point.
1143 crl_issuer: List of CRL issuers associated with the distribution
1144 point.
1145 """
1147 full_name: list[GeneralName] | None = None
1148 name_relative_to_crl_issuer: RelativeDistinguishedName | None = None
1149 reasons: list[Reason] | None = None
1150 crl_issuer: list[GeneralName] | None = None
1152 @classmethod
1153 def from_cryptography(
1154 cls: type["DistributionPoint"], extension: x509.DistributionPoint
1155 ) -> "DistributionPoint":
1156 """
1157 Constructs a DistributionPoint object from a cryptography
1158 DistributionPoint object.
1160 Args:
1161 extension: The cryptography DistributionPoint object.
1163 Returns:
1164 DistributionPoint: The constructed DistributionPoint object.
1165 """
1166 full_names = None
1167 if extension.full_name is not None:
1168 full_names = []
1169 for full_name in extension.full_name:
1170 full_names.append(GeneralName.from_cryptography(full_name))
1172 relative_name = None
1173 if extension.relative_name is not None:
1174 relative_name = RelativeDistinguishedName.from_cryptography(
1175 extension.relative_name
1176 )
1178 reasons = None
1179 if extension.reasons is not None:
1180 reasons = []
1181 for reason in extension.reasons:
1182 reasons.append(getattr(Reason, reason.name))
1184 crl_issuers = None
1185 if extension.crl_issuer is not None:
1186 crl_issuers = []
1187 for crl_issuer in extension.crl_issuer:
1188 crl_issuers.append(GeneralName.from_cryptography(crl_issuer))
1190 return cls(
1191 full_name=full_names,
1192 name_relative_to_crl_issuer=relative_name,
1193 reasons=reasons,
1194 crl_issuer=crl_issuers,
1195 _x509_obj=extension,
1196 )
1198 def _string_dict(self):
1199 ret = {}
1200 if self.full_name is not None:
1201 ret["Full Name"] = []
1202 for full_name in self.full_name:
1203 ret["Full Name"].append(full_name._string_dict())
1204 if self.name_relative_to_crl_issuer is not None:
1205 ret["Name Relative To CRL Issuer"] = (
1206 self.name_relative_to_crl_issuer._string_dict()
1207 )
1208 if self.reasons is not None:
1209 ret["Reasons"] = []
1210 for reason in self.reasons:
1211 ret["Reasons"].append(
1212 getattr(x509.ReasonFlags, reason.name).value
1213 )
1214 if self.crl_issuer is not None:
1215 ret["CRL Issuer"] = []
1216 for crl_issuer in self.crl_issuer:
1217 ret["CRL Issuer"].append(crl_issuer._string_dict())
1218 return ret
1220 def _to_cryptography(self) -> x509.DistributionPoint:
1221 full_names = None
1222 if self.full_name is not None:
1223 full_names = []
1224 for full_name in self.full_name:
1225 full_names.append(full_name._to_cryptography())
1227 relative_names = None
1228 if self.name_relative_to_crl_issuer is not None:
1229 relative_names = (
1230 self.name_relative_to_crl_issuer._to_cryptography()
1231 )
1233 reasons = None
1234 if self.reasons is not None:
1235 reasons = []
1236 for reason in self.reasons:
1237 reasons.append(getattr(x509.ReasonFlags, reason.name))
1238 reasons = frozenset(reasons)
1240 crl_issuers = None
1241 if self.crl_issuer is not None:
1242 crl_issuers = []
1243 for crl_issuer in self.crl_issuer:
1244 crl_issuers.append(crl_issuer._to_cryptography())
1246 return x509.DistributionPoint(
1247 full_name=full_names,
1248 relative_name=relative_names,
1249 reasons=reasons,
1250 crl_issuer=crl_issuers,
1251 )
1254class CrlDistributionPoints(Extension):
1255 """
1256 Represents the CRL Distribution Points extension in X.509 certificates.
1258 Attributes:
1259 crl_distribution_points: List of CRL distribution points.
1260 """
1262 crl_distribution_points: list[DistributionPoint]
1264 def __iter__(self) -> Iterator[DistributionPoint]:
1265 return iter(self.crl_distribution_points)
1267 @classmethod
1268 def from_cryptography(
1269 cls: type["CrlDistributionPoints"],
1270 extension: x509.CRLDistributionPoints,
1271 ) -> "CrlDistributionPoints":
1272 """
1273 Constructs a CrlDistributionPoints object from a cryptography
1274 CRLDistributionPoints object.
1276 Args:
1277 extension: The cryptography CRLDistributionPoints object.
1279 Returns:
1280 The constructed CrlDistributionPoints object.
1281 """
1282 crl_distribution_points = []
1283 for crl_distribution_point in extension:
1284 parsed = DistributionPoint.from_cryptography(
1285 crl_distribution_point
1286 )
1287 crl_distribution_points.append(parsed)
1289 return cls(
1290 crl_distribution_points=crl_distribution_points,
1291 _x509_obj=extension,
1292 )
1294 def _string_dict(self):
1295 ret = {self.name: []}
1296 for dist_point in self.crl_distribution_points:
1297 ret[self.name].append(dist_point._string_dict())
1299 return ret
1301 def _to_cryptography(self) -> x509.CRLDistributionPoints:
1302 dist_points = []
1303 for dist_point in self.crl_distribution_points:
1304 dist_points.append(dist_point._to_cryptography())
1306 return x509.CRLDistributionPoints(dist_points)
1309class IssuingDistributionPoint(Extension):
1310 """
1311 Represents the Issuing Distribution Points extension in X.509 certificates.
1313 Attributes:
1314 full_name: List of full names associated with the distribution point.
1315 name_relative_to_crl_issuer: Relative name to the CRL issuer.
1316 """
1318 full_name: list[GeneralName] | None = None
1319 name_relative_to_crl_issuer: RelativeDistinguishedName | None = None
1320 only_contains_user_certs: bool = False
1321 only_contains_ca_certs: bool = False
1322 indirect_crl: bool = False
1323 only_contains_attribute_certs: bool = False
1324 only_some_reasons: list[Reason] | None = None
1326 @classmethod
1327 def from_cryptography(
1328 cls: type["IssuingDistributionPoint"],
1329 extension: x509.IssuingDistributionPoint,
1330 ) -> "IssuingDistributionPoint":
1331 """
1332 Constructs a IssuingDistributionPoint object from a cryptography
1333 IssuingDistributionPoint object.
1335 Args:
1336 extension: The cryptography IssuingDistributionPoint object.
1338 Returns:
1339 IssuingDistributionPoint: The constructed IssuingDistributionPoint
1340 object.
1341 """
1342 full_names = None
1343 if extension.full_name is not None:
1344 full_names = []
1345 for full_name in extension.full_name:
1346 full_names.append(GeneralName.from_cryptography(full_name))
1348 relative_name = None
1349 if extension.relative_name is not None:
1350 relative_name = RelativeDistinguishedName.from_cryptography(
1351 extension.relative_name
1352 )
1354 reasons = None
1355 if extension.only_some_reasons is not None:
1356 reasons = []
1357 for reason in extension.only_some_reasons:
1358 reasons.append(getattr(Reason, reason.name))
1360 return cls(
1361 full_name=full_names,
1362 name_relative_to_crl_issuer=relative_name,
1363 only_contains_user_certs=extension.only_contains_user_certs,
1364 only_contains_ca_certs=extension.only_contains_ca_certs,
1365 only_contains_attribute_certs=extension.only_contains_attribute_certs,
1366 indirect_crl=extension.indirect_crl,
1367 only_some_reasons=reasons,
1368 _x509_obj=extension,
1369 )
1371 def _string_dict(self):
1372 ret = {
1373 "Only User Certs": self.only_contains_user_certs,
1374 "Only CA Certs": self.only_contains_ca_certs,
1375 "Only Attribute Certs": self.only_contains_attribute_certs,
1376 "Indirect CRL": self.indirect_crl,
1377 }
1378 if self.full_name is not None:
1379 ret["Full Name"] = []
1380 for full_name in self.full_name:
1381 ret["Full Name"].append(full_name._string_dict())
1382 if self.name_relative_to_crl_issuer is not None:
1383 ret["Name Relative To CRL Issuer"] = (
1384 self.name_relative_to_crl_issuer._string_dict()
1385 )
1386 return ret
1388 def _to_cryptography(self) -> x509.IssuingDistributionPoint:
1389 full_names = None
1390 if self.full_name is not None:
1391 full_names = []
1392 for full_name in self.full_name:
1393 full_names.append(full_name._to_cryptography())
1395 relative_names = None
1396 if self.name_relative_to_crl_issuer is not None:
1397 relative_names = (
1398 self.name_relative_to_crl_issuer._to_cryptography()
1399 )
1401 reasons = None
1402 if self.only_some_reasons is not None:
1403 reasons = []
1404 for reason in self.only_some_reasons:
1405 reasons.append(getattr(x509.ReasonFlags, reason.name))
1406 reasons = frozenset(reasons)
1408 return x509.IssuingDistributionPoint(
1409 full_name=full_names,
1410 relative_name=relative_names,
1411 only_contains_user_certs=self.only_contains_user_certs,
1412 only_contains_ca_certs=self.only_contains_ca_certs,
1413 only_contains_attribute_certs=self.only_contains_attribute_certs,
1414 indirect_crl=self.indirect_crl,
1415 only_some_reasons=reasons,
1416 )
1419class InhibitAnyPolicy(Extension):
1420 """
1421 Represents the Inhibit Any Policy extension in X.509 certificates.
1423 Attributes:
1424 skip_certs: Number of certificates to skip.
1425 """
1427 skip_certs: int
1429 @classmethod
1430 def from_cryptography(
1431 cls: type["InhibitAnyPolicy"], extension: x509.InhibitAnyPolicy
1432 ) -> "InhibitAnyPolicy":
1433 """
1434 Constructs an InhibitAnyPolicy object from a cryptography
1435 InhibitAnyPolicy object.
1437 Args:
1438 extension: The cryptography InhibitAnyPolicy object.
1440 Returns:
1441 The constructed InhibitAnyPolicy object.
1442 """
1443 return cls(
1444 skip_certs=extension.skip_certs,
1445 _x509_obj=extension,
1446 )
1448 def _string_dict(self):
1449 return {self.name: {"Skip Certs": self.skip_certs}}
1451 def _to_cryptography(self) -> x509.InhibitAnyPolicy:
1452 return x509.InhibitAnyPolicy(self.skip_certs)
1455class FreshestCrl(CrlDistributionPoints):
1456 """
1457 Represents the Freshest CRL extension in X.509 certificates.
1458 """
1460 def _to_cryptography(self) -> x509.FreshestCRL:
1461 dist_points = []
1462 for dist_point in self.crl_distribution_points:
1463 dist_points.append(dist_point._to_cryptography())
1465 return x509.FreshestCRL(dist_points)
1468class AccessDescriptionId(Enum):
1469 """
1470 Enumeration of access description identifiers for X.509 certificates.
1471 """
1473 CA_ISSUERS = "1.3.6.1.5.5.7.48.2"
1474 OCSP = "1.3.6.1.5.5.7.48.1"
1477class AccessDescription(CryptoParser):
1478 """
1479 Represents an Access Description in X.509 certificates.
1481 Attributes:
1482 access_method: The access method identifier.
1483 access_location: The access location.
1484 """
1486 access_method: AccessDescriptionId
1487 access_location: GeneralName
1489 @classmethod
1490 def from_cryptography(
1491 cls: type["AccessDescription"], extension: x509.AccessDescription
1492 ) -> "AccessDescription":
1493 """
1494 Constructs an AccessDescription object from a cryptography
1495 AccessDescription object.
1497 Args:
1498 extension: The cryptography AccessDescription object.
1500 Returns:
1501 The constructed AccessDescription object.
1502 """
1503 access_method = AccessDescriptionId(
1504 extension.access_method.dotted_string
1505 )
1506 return cls(
1507 access_method=access_method,
1508 access_location=GeneralName.from_cryptography(
1509 extension.access_location
1510 ),
1511 _x509_obj=extension,
1512 )
1514 def _string_dict(self):
1515 return {
1516 "Access Method": self.access_method.name,
1517 "Access Location": self.access_location._string_dict(),
1518 }
1520 def _to_cryptography(self) -> x509.AccessDescription:
1521 return x509.AccessDescription(
1522 access_method=getattr(
1523 AuthorityInformationAccessOID, self.access_method.name
1524 ),
1525 access_location=self.access_location._to_cryptography(),
1526 )
1529class AuthorityInformationAccess(Extension):
1530 """
1531 Represents the Authority Information Access extension in X.509
1532 certificates.
1534 Attributes:
1535 access_description: List of access descriptions.
1536 """
1538 access_description: list[AccessDescription]
1540 def __iter__(self) -> Iterator[AccessDescription]:
1541 return iter(self.access_description)
1543 @classmethod
1544 def from_cryptography(
1545 cls: type["AuthorityInformationAccess"],
1546 extension: x509.AuthorityInformationAccess,
1547 ) -> "AuthorityInformationAccess":
1548 """
1549 Constructs an AuthorityInformationAccess object from a
1550 cryptography AuthorityInformationAccess object.
1552 Args:
1553 extension: The cryptography AuthorityInformationAccess object.
1555 Returns:
1556 The constructed AuthorityInformationAccess object.
1557 """
1558 access_descriptions = []
1559 for access_description in extension:
1560 access_descriptions.append(
1561 AccessDescription.from_cryptography(access_description)
1562 )
1563 return cls(
1564 access_description=access_descriptions,
1565 _x509_obj=extension,
1566 )
1568 def _string_dict(self):
1569 access_descriptions = []
1570 for access_description in self.access_description:
1571 access_descriptions.append(access_description._string_dict())
1573 return {self.name: {"Access Description": access_descriptions}}
1575 def _to_cryptography(self) -> x509.AuthorityInformationAccess:
1576 access_descriptions = []
1577 for access_description in self.access_description:
1578 access_descriptions.append(access_description._to_cryptography())
1579 return x509.AuthorityInformationAccess(access_descriptions)
1582class SubjectInformationAccess(AuthorityInformationAccess):
1583 """
1584 Represents the Subject Information Access extension in X.509
1585 certificates.
1586 """
1588 def _to_cryptography(self) -> x509.SubjectInformationAccess:
1589 access_descriptions = []
1590 for access_description in self.access_description:
1591 access_descriptions.append(access_description._to_cryptography())
1592 return x509.SubjectInformationAccess(access_descriptions)
1595class Extensions(CryptoParser):
1596 """
1597 Extensions in X.509 certificates.
1599 Attributes:
1600 authority_key_identifier: Authority Key Identifier extension.
1601 subject_key_identifier: Subject Key Identifier extension.
1602 key_usage: Key Usage extension.
1603 certificate_policies: Certificate Policies extension.
1604 subject_alternative_name: Subject Alternative Name extension.
1605 issuer_alternative_name: Issuer Alternative Name extension.
1606 subject_directory_attributes: Subject Directory Attributes extension.
1607 basic_constraints: Basic Constraints extension.
1608 name_constraints: Name Constraints extension.
1609 policy_constraints: Policy Constraints extension.
1610 extended_key_usage: Extended Key Usage extension.
1611 crl_distribution_points: CRL Distribution Points extension.
1612 issuing_distribution_point: Issuer Distribution Points extension.
1613 inhibit_any_policy: Inhibit Any Policy extension.
1614 freshest_crl: Freshest CRL extension.
1615 authority_information_access: Authority Information Access extension.
1616 subject_information_access: Subject Information Access extension.
1617 """
1619 model_config = ConfigDict(populate_by_name=True)
1621 authority_key_identifier: AuthorityKeyIdentifier | None = Field(
1622 alias=ExtensionOID.AUTHORITY_KEY_IDENTIFIER.dotted_string, default=None
1623 )
1624 subject_key_identifier: SubjectKeyIdentifier | None = Field(
1625 alias=ExtensionOID.SUBJECT_KEY_IDENTIFIER.dotted_string, default=None
1626 )
1627 key_usage: KeyUsage | None = Field(
1628 alias=ExtensionOID.KEY_USAGE.dotted_string, default=None
1629 )
1630 certificate_policies: CertificatePolicies | None = Field(
1631 alias=ExtensionOID.CERTIFICATE_POLICIES.dotted_string, default=None
1632 )
1633 # policy_mappings
1634 subject_alternative_name: SubjectAlternativeName | None = Field(
1635 alias=ExtensionOID.SUBJECT_ALTERNATIVE_NAME.dotted_string, default=None
1636 )
1637 issuer_alternative_name: IssuerAlternativeName | None = Field(
1638 alias=ExtensionOID.ISSUER_ALTERNATIVE_NAME.dotted_string, default=None
1639 )
1640 subject_directory_attributes: SubjectDirectoryAttributes | None = Field(
1641 alias=ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES.dotted_string,
1642 default=None,
1643 )
1644 basic_constraints: BasicConstraints | None = Field(
1645 alias=ExtensionOID.BASIC_CONSTRAINTS.dotted_string,
1646 default=None,
1647 )
1648 name_constraints: NameConstraints | None = Field(
1649 alias=ExtensionOID.NAME_CONSTRAINTS.dotted_string,
1650 default=None,
1651 )
1652 policy_constraints: PolicyConstraints | None = Field(
1653 alias=ExtensionOID.POLICY_CONSTRAINTS.dotted_string,
1654 default=None,
1655 )
1656 extended_key_usage: ExtendedKeyUsage | None = Field(
1657 alias=ExtensionOID.EXTENDED_KEY_USAGE.dotted_string,
1658 default=None,
1659 )
1660 crl_distribution_points: CrlDistributionPoints | None = Field(
1661 alias=ExtensionOID.CRL_DISTRIBUTION_POINTS.dotted_string,
1662 default=None,
1663 )
1664 issuing_distribution_point: IssuingDistributionPoint | None = Field(
1665 alias=ExtensionOID.ISSUING_DISTRIBUTION_POINT.dotted_string,
1666 default=None,
1667 )
1668 inhibit_any_policy: InhibitAnyPolicy | None = Field(
1669 alias=ExtensionOID.INHIBIT_ANY_POLICY.dotted_string,
1670 default=None,
1671 )
1672 freshest_crl: FreshestCrl | None = Field(
1673 alias=ExtensionOID.FRESHEST_CRL.dotted_string,
1674 default=None,
1675 )
1676 authority_information_access: AuthorityInformationAccess | None = Field(
1677 alias=ExtensionOID.AUTHORITY_INFORMATION_ACCESS.dotted_string,
1678 default=None,
1679 )
1680 subject_information_access: SubjectInformationAccess | None = Field(
1681 alias=ExtensionOID.SUBJECT_INFORMATION_ACCESS.dotted_string,
1682 default=None,
1683 )
1685 def __iter__(self) -> Iterator[Extension]:
1686 for field_name in Extensions.model_fields:
1687 val = getattr(self, field_name)
1688 if val is None:
1689 continue
1691 yield getattr(self, field_name)
1693 @classmethod
1694 def from_cryptography(
1695 cls: type["Extensions"], cert_extensions: x509.Extensions
1696 ) -> "Extensions":
1697 """
1698 Constructs an Extensions object from cryptography X.509 Extensions.
1700 Args:
1701 cert_extensions: The cryptography X.509 Extensions.
1703 Returns:
1704 The constructed Extensions object.
1705 """
1706 extensions_dict = {"_x509_obj": cert_extensions}
1708 for field_info in Extensions.model_fields.values():
1709 class_type = typing.get_args(field_info.annotation)[0]
1710 oid = ObjectIdentifier(field_info.alias)
1711 try:
1712 x509_ext = cert_extensions.get_extension_for_oid(oid)
1714 ext = class_type.from_cryptography(x509_ext.value)
1716 extensions_dict[oid.dotted_string] = ext
1717 extensions_dict[oid.dotted_string].critical = x509_ext.critical
1718 except ExtensionNotFound:
1719 logger.trace(f"Extension with OID: {oid._name} not found")
1721 return cls(**extensions_dict)
1723 @staticmethod
1724 def _get_extension_from_oid(
1725 cert_extensions: x509.Extensions, oid: ObjectIdentifier, classType
1726 ) -> ExtensionTypeVar | None:
1727 try:
1728 ext_val = cert_extensions.get_extension_for_oid(oid).value
1729 classType.from_cryptography(ext_val)
1730 except ExtensionNotFound:
1731 logger.debug(f"Extension with OID: {oid._name} not found")
1732 return None
1734 def _string_dict(self):
1735 extensions = {}
1736 for field_name in Extensions.model_fields:
1737 att_val = getattr(self, field_name)
1739 if att_val is None or str(att_val) == "":
1740 continue
1742 extensions.update(att_val._string_dict())
1744 return extensions
1746 def _to_cryptography(self) -> x509.Extensions:
1747 extensions = []
1748 for field_name in Extensions.model_fields:
1749 att_val = getattr(self, field_name)
1751 if att_val is None or str(att_val) == "":
1752 continue
1754 extensions.append(att_val._to_cryptography())
1756 return x509.Extensions(extensions)