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

1import importlib 

2import typing 

3from collections.abc import Iterator 

4from enum import Enum 

5 

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 

18 

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 

22 

23GENERAL_NAME_MODULE = importlib.import_module("cryptography.x509.general_name") 

24EXTENSIONS_MODULE = importlib.import_module("cryptography.x509.extensions") 

25 

26 

27class Extension(CryptoParser): 

28 """ 

29 Represents a cryptographic extension. 

30 

31 Attributes: 

32 critical: Indicates whether the extension is critical. 

33 """ 

34 

35 critical: bool | None = False 

36 

37 @property 

38 def name(self) -> str: 

39 """ 

40 Get the name of the extension. 

41 

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 

51 

52 

53class GeneralName(CryptoParser): 

54 """ 

55 Represents a general name. 

56 

57 Attributes: 

58 name: The name of the general name. 

59 value: The value of the general name. 

60 """ 

61 

62 name: str 

63 value: str | Name 

64 

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. 

71 

72 Args: 

73 crypto_obj: The cryptography GeneralName object. 

74 

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) 

88 

89 return globals()[crypto_obj.__class__.__name__](crypto_obj.value) 

90 

91 def _to_cryptography(self) -> x509.GeneralName: 

92 return getattr(GENERAL_NAME_MODULE, self.name)(self.value) 

93 

94 def _string_dict(self) -> dict[str, str]: 

95 val = self.value 

96 if isinstance(self.value, Name): 

97 val = str(self.value) 

98 

99 return { 

100 "name": self.name, 

101 "value": val, 

102 } 

103 

104 

105class DnsName(GeneralName): 

106 """ 

107 Represents a DNS name. 

108 

109 Attributes: 

110 value: The DNS name value. 

111 """ 

112 

113 def __init__(self, value: str): 

114 super().__init__(name="DNSName", value=value) 

115 

116 

117class DirectoryName(GeneralName): 

118 """ 

119 Represents a directory name. 

120 

121 Attributes: 

122 value: The directory name value. 

123 """ 

124 

125 def __init__(self, value: Name): 

126 super().__init__(name="DirectoryName", value=value) 

127 

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. 

135 

136 Args: 

137 crypto_obj: The cryptography DirectoryName object. 

138 

139 Returns: 

140 The DirectoryName instance. 

141 """ 

142 return cls(value=Name.from_cryptography(crypto_obj.value)) 

143 

144 def _to_cryptography(self) -> x509.GeneralName: 

145 return x509.DirectoryName(self.value._to_cryptography()) 

146 

147 

148class IpAddress(GeneralName): 

149 """ 

150 Represents an IP address. Can either be a IPv4/IPv6 single address or 

151 network. 

152 

153 Attributes: 

154 value: The IP address or network 

155 """ 

156 

157 def __init__(self, value: str): 

158 super().__init__(name="IPAddress", value=value) 

159 

160 def _to_cryptography(self) -> x509.GeneralName: 

161 cls_name = "IPv4" 

162 if ":" in self.value: 

163 cls_name = "IPv6" 

164 

165 if "/" in self.value: 

166 cls_name += "Network" 

167 else: 

168 cls_name += "Address" 

169 

170 module = importlib.import_module("ipaddress") 

171 value = getattr(module, cls_name)(self.value) 

172 return x509.IPAddress(value) 

173 

174 

175class OtherName(GeneralName): 

176 """ 

177 Represents an other name. 

178 

179 Attributes: 

180 value: The other name value. 

181 oid: The object identifier (OID) of the other name. 

182 """ 

183 

184 oid: str 

185 

186 def __init__(self, value: str, oid: str): 

187 super().__init__(name="OtherName", value=value, oid=oid) 

188 

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. 

195 

196 Args: 

197 crypto_obj: The cryptography OtherName object. 

198 

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 ) 

206 

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 ) 

212 

213 

214class RFC822Name(GeneralName): 

215 """ 

216 Represents an RFC822 name. 

217 

218 Attributes: 

219 value: The RFC822 name value. 

220 """ 

221 

222 def __init__(self, value: str): 

223 super().__init__(name="RFC822Name", value=value) 

224 

225 

226class RegisteredId(GeneralName): 

227 """ 

228 Represents a registered ID. 

229 

230 Attributes: 

231 value: The registered ID value. 

232 """ 

233 

234 def __init__(self, value: str): 

235 super().__init__(name="RegisteredID", value=value) 

236 

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. 

243 

244 Args: 

245 crypto_obj: The cryptography RegisteredId object. 

246 

247 Returns: 

248 The RegisteredId instance. 

249 """ 

250 return cls(value=str(crypto_obj.value.dotted_string)) 

251 

252 def _to_cryptography(self) -> x509.RegisteredID: 

253 return x509.RegisteredID(x509.ObjectIdentifier(self.value)) 

254 

255 

256class UniformResourceIdentifier(GeneralName): 

257 """ 

258 Represents a uniform resource identifier (URI). 

259 

260 Attributes: 

261 value: The URI value. 

262 """ 

263 

264 def __init__(self, value: str): 

265 super().__init__(name="UniformResourceIdentifier", value=value) 

266 

267 

268class AuthorityKeyIdentifier(Extension): 

269 """ 

270 Represents an authority key identifier extension. 

271 

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 """ 

278 

279 key_identifier: bytes | None 

280 authority_cert_issuer: list[GeneralName] | None 

281 authority_cert_serial_number: int | None 

282 

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. 

291 

292 Args: 

293 extension: The cryptography AuthorityKeyIdentifier object. 

294 

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)) 

303 

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 ) 

310 

311 def _string_dict(self): 

312 ret = {} 

313 

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 ) 

326 

327 if ret: 

328 return {self.name: ret} 

329 return {} 

330 

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) 

338 

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 ) 

344 

345 

346class SubjectKeyIdentifier(Extension): 

347 """ 

348 Represents a subject key identifier extension. 

349 

350 Attributes: 

351 subject_key_identifier: The subject key identifier. 

352 """ 

353 

354 subject_key_identifier: bytes 

355 

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. 

363 

364 Args: 

365 extension: The cryptography SubjectKeyIdentifier object. 

366 

367 Returns: 

368 The SubjectKeyIdentifier instance. 

369 """ 

370 return cls( 

371 subject_key_identifier=extension.key_identifier, 

372 _x509_obj=extension, 

373 ) 

374 

375 def _string_dict(self): 

376 hex_key = _byte_to_hex(self.subject_key_identifier) 

377 return {self.name: {"Subject Key Identifier": hex_key}} 

378 

379 def _to_cryptography(self) -> x509.SubjectKeyIdentifier: 

380 return x509.SubjectKeyIdentifier(self.subject_key_identifier) 

381 

382 

383class KeyUsage(Extension): 

384 """ 

385 Represents a key usage extension. 

386 

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 """ 

398 

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 

408 

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. 

415 

416 Args: 

417 extension: The cryptography KeyUsage object. 

418 

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 

430 

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 ) 

443 

444 def _string_dict(self): 

445 true_fields = [] 

446 for field in KeyUsage.model_fields: 

447 if field == "critical": 

448 continue 

449 

450 field_title = " ".join(ele.title() for ele in field.split("_")) 

451 if getattr(self, field): 

452 true_fields.append(field_title) 

453 

454 return {self.name: ", ".join(true_fields)} 

455 

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 ) 

468 

469 

470class NoticeReference(Extension): 

471 """ 

472 Represents a notice reference extension. 

473 

474 Attributes: 

475 organization: The organization associated with the notice. 

476 notice_numbers: List of notice numbers. 

477 """ 

478 

479 organization: str 

480 notice_numbers: list[int] 

481 

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 ) 

489 

490 def _string_dict(self): 

491 return { 

492 self.name: { 

493 "Organization": self.organization, 

494 "Notice Numbers": self.notice_numbers, 

495 } 

496 } 

497 

498 def _to_cryptography(self) -> x509.NoticeReference: 

499 return x509.NoticeReference( 

500 organization=self.organization, 

501 notice_numbers=self.notice_numbers, 

502 ) 

503 

504 

505class UserNotice(Extension): 

506 """ 

507 Represents a user notice extension. 

508 

509 Attributes: 

510 notice_reference: The notice reference associated with the user notice. 

511 explicit_text: The explicit text of the user notice. 

512 """ 

513 

514 notice_reference: NoticeReference | None 

515 explicit_text: str | None 

516 

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. 

523 

524 Args: 

525 policy_info: The cryptography UserNotice object. 

526 

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 ) 

537 

538 def _string_dict(self): 

539 ret = {self.name: {}} 

540 

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 

546 

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 ) 

552 

553 

554class PolicyInformation(Extension): 

555 """ 

556 Represents a policy information extension. 

557 

558 Attributes: 

559 policy_identifier: The policy identifier associated with the policy 

560 information. 

561 policy_qualifiers: List of policy qualifiers. 

562 """ 

563 

564 policy_identifier: str 

565 policy_qualifiers: list[str | UserNotice] | None 

566 

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. 

574 

575 Args: 

576 policy_info: The cryptography PolicyInformation object. 

577 

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 ) 

591 

592 return cls( 

593 policy_identifier=policy_info.policy_identifier.dotted_string, 

594 policy_qualifiers=policy_qualifiers, 

595 _x509_obj=policy_info, 

596 ) 

597 

598 def _string_dict(self): 

599 name = f"Policy {self.policy_identifier}" 

600 ret = {name: []} 

601 

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 

609 

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()) 

617 

618 return x509.PolicyInformation( 

619 policy_identifier=x509.ObjectIdentifier(self.policy_identifier), 

620 policy_qualifiers=qualifiers, 

621 ) 

622 

623 

624class CertificatePolicies(Extension): 

625 """ 

626 Represents a certificate policies extension. 

627 

628 Attributes: 

629 policy_information: List of policy information. 

630 """ 

631 

632 policy_information: list[PolicyInformation] 

633 

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. 

641 

642 Args: 

643 extension: The cryptography CertificatePolicies object. 

644 

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) 

652 

653 return cls( 

654 policy_information=res, 

655 _x509_obj=extension, 

656 ) 

657 

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()) 

662 

663 return ret 

664 

665 def _to_cryptography(self) -> x509.CertificatePolicies: 

666 policies = [] 

667 for policy in self.policy_information: 

668 policies.append(policy._to_cryptography()) 

669 

670 return x509.CertificatePolicies(policies) 

671 

672 

673class AlternativeName(Extension): 

674 """ 

675 Represents an alternative name extension. 

676 

677 Attributes: 

678 general_names: List of general names. 

679 """ 

680 

681 general_names: list[GeneralName] 

682 

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. 

691 

692 Args: 

693 extension: The cryptography SubjectAlternativeName or 

694 IssuerAlternativeName object. 

695 

696 Returns: 

697 The AlternativeName instance. 

698 """ 

699 names = [] 

700 for general_name in extension: 

701 names.append(GeneralName.from_cryptography(general_name)) 

702 

703 return cls( 

704 general_names=names, 

705 _x509_obj=extension, 

706 ) 

707 

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} 

713 

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()) 

720 

721 subclass_name = type(self).__name__ 

722 

723 return getattr(EXTENSIONS_MODULE, subclass_name)(general_names) 

724 

725 

726class SubjectAlternativeName(AlternativeName): 

727 """ 

728 Represents a subject alternative name extension. 

729 """ 

730 

731 

732class IssuerAlternativeName(AlternativeName): 

733 """ 

734 Represents an issuer alternative name extension. 

735 """ 

736 

737 

738class SubjectDirectoryAttributes(Extension): 

739 """ 

740 Represents a subject directory attributes extension. 

741 

742 Attributes: 

743 attributes: List of attributes. 

744 """ 

745 

746 attributes: list[str] 

747 

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. 

756 

757 Args: 

758 extension: The cryptography UnrecognizedExtension object. 

759 

760 Returns: 

761 The SubjectDirectoryAttributes instance. 

762 """ 

763 attributes = [] 

764 vals = extension.value 

765 if not isinstance(vals, list): 

766 vals = [vals] 

767 

768 for val in vals: 

769 if isinstance(val, bytes): 

770 val = _byte_to_hex(val) 

771 

772 attributes.append(val) 

773 

774 return cls(attributes=attributes, _x509_obj=extension) 

775 

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 

783 

784 values.append(attribute) 

785 

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 ) 

793 

794 def _string_dict(self): 

795 return {self.name: self.attributes} 

796 

797 

798class BasicConstraints(Extension): 

799 """ 

800 Represents a basic constraints extension. 

801 

802 Attributes: 

803 ca: Indicates if the certificate is a CA. 

804 path_len_constrain: The path length constraint. 

805 """ 

806 

807 ca: bool 

808 path_len_constraint: int | None = None 

809 

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. 

817 

818 Args: 

819 extension: The cryptography BasicConstraints object. 

820 

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 ) 

829 

830 def _string_dict(self): 

831 ret = { 

832 self.name: { 

833 "CA": self.ca, 

834 } 

835 } 

836 

837 if self.path_len_constraint is not None: 

838 ret["Path Lenght"] = self.path_len_constraint 

839 

840 return ret 

841 

842 def _to_cryptography(self) -> x509.BasicConstraints: 

843 return x509.BasicConstraints( 

844 ca=self.ca, 

845 path_length=self.path_len_constraint, 

846 ) 

847 

848 

849class NameConstraints(Extension): 

850 """ 

851 Represents a name constraints extension. 

852 

853 Attributes: 

854 permitted_subtrees: List of permitted subtrees. 

855 excluded_subtrees: List of excluded subtrees. 

856 """ 

857 

858 permitted_subtrees: list[GeneralName] | None 

859 excluded_subtrees: list[GeneralName] | None 

860 

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 

868 

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 

874 

875 return cls( 

876 permitted_subtrees=permitted_subtrees, 

877 excluded_subtrees=excluded_subtrees, 

878 _x509_obj=extension, 

879 ) 

880 

881 def _string_dict(self): 

882 permitted_subtrees = [] 

883 for permitted_subtree in self.permitted_subtrees: 

884 permitted_subtrees.append(permitted_subtree._string_dict()) 

885 

886 excluded_subtrees = [] 

887 for excluded_subtree in self.excluded_subtrees: 

888 excluded_subtrees.append(excluded_subtree._string_dict()) 

889 

890 return { 

891 self.name: { 

892 "Permitted Subtrees": permitted_subtrees, 

893 "Excluded Subtrees": excluded_subtrees, 

894 } 

895 } 

896 

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()) 

903 

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()) 

909 

910 return x509.NameConstraints( 

911 permitted_subtrees=permitted_subtrees, 

912 excluded_subtrees=excluded_subtrees, 

913 ) 

914 

915 

916class PolicyConstraints(Extension): 

917 """ 

918 Represents a policy constraints extension. 

919 

920 Attributes: 

921 require_explicit_policy: The require explicit policy value. 

922 inhibit_policy_mapping: The inhibit policy mapping value. 

923 """ 

924 

925 require_explicit_policy: int | None 

926 inhibit_policy_mapping: int | None 

927 

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 ) 

935 

936 def _string_dict(self): 

937 ret = {self.name: {}} 

938 

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 ) 

947 

948 return ret 

949 

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 ) 

955 

956 

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} 

970 

971 

972class ExtendedKeyUsage(Extension): 

973 """ 

974 Represents the Extended Key Usage extension in X.509 certificates. 

975 

976 Attributes: 

977 ext_key_usage_syntax: List of extended key usage OIDs. 

978 """ 

979 

980 ext_key_usage_syntax: list[str] 

981 

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. 

989 

990 Args: 

991 extension: The cryptography ExtendedKeyUsage object. 

992 

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 ) 

1003 

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 ) 

1013 

1014 return {self.name: names} 

1015 

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) 

1021 

1022 

1023class AttributeTypeAndValue(CryptoParser): 

1024 """ 

1025 Represents an Attribute Type and Value in X.509 certificates. 

1026 

1027 Attributes: 

1028 oid: The Object Identifier (OID) of the attribute. 

1029 value: The value of the attribute. 

1030 """ 

1031 

1032 oid: str 

1033 value: str 

1034 

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. 

1042 

1043 Args: 

1044 x509_obj: The cryptography NameAttribute object. 

1045 

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 ) 

1054 

1055 def _to_cryptography(self) -> x509.NameAttribute: 

1056 return x509.NameAttribute( 

1057 oid=x509.ObjectIdentifier(self.oid), 

1058 value=self.value, 

1059 ) 

1060 

1061 def _string_dict(self) -> dict[str, str]: 

1062 return { 

1063 "OID": self.oid, 

1064 "Value": self.value, 

1065 } 

1066 

1067 

1068class RelativeDistinguishedName(CryptoParser): 

1069 """ 

1070 Represents a Relative Distinguished Name in X.509 certificates. 

1071 

1072 Attributes: 

1073 attributes: List of attributes in the RDN. 

1074 """ 

1075 

1076 attributes: list[AttributeTypeAndValue] 

1077 

1078 def __iter__(self) -> Iterator: 

1079 return iter(self.attributes) 

1080 

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. 

1089 

1090 Args: 

1091 x509_obj: The cryptography RelativeDistinguishedName object. 

1092 

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 ) 

1101 

1102 cls(attributes=attributes, _x509_obj=x509_obj) 

1103 

1104 def _string_dict(self) -> dict: 

1105 attributes = [] 

1106 for att in self.attributes: 

1107 attributes.append(att._string_dict()) 

1108 

1109 return {"RelativeDistinguishedName": attributes} 

1110 

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) 

1116 

1117 

1118class Reason(Enum): 

1119 """ 

1120 Reasons for the DistributionPoint extension 

1121 """ 

1122 

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" 

1133 

1134 

1135class DistributionPoint(CryptoParser): 

1136 """ 

1137 Represents a Distribution Point in X.509 certificates. 

1138 

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 """ 

1146 

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 

1151 

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. 

1159 

1160 Args: 

1161 extension: The cryptography DistributionPoint object. 

1162 

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)) 

1171 

1172 relative_name = None 

1173 if extension.relative_name is not None: 

1174 relative_name = RelativeDistinguishedName.from_cryptography( 

1175 extension.relative_name 

1176 ) 

1177 

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)) 

1183 

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)) 

1189 

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 ) 

1197 

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 

1219 

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()) 

1226 

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 ) 

1232 

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) 

1239 

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()) 

1245 

1246 return x509.DistributionPoint( 

1247 full_name=full_names, 

1248 relative_name=relative_names, 

1249 reasons=reasons, 

1250 crl_issuer=crl_issuers, 

1251 ) 

1252 

1253 

1254class CrlDistributionPoints(Extension): 

1255 """ 

1256 Represents the CRL Distribution Points extension in X.509 certificates. 

1257 

1258 Attributes: 

1259 crl_distribution_points: List of CRL distribution points. 

1260 """ 

1261 

1262 crl_distribution_points: list[DistributionPoint] 

1263 

1264 def __iter__(self) -> Iterator[DistributionPoint]: 

1265 return iter(self.crl_distribution_points) 

1266 

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. 

1275 

1276 Args: 

1277 extension: The cryptography CRLDistributionPoints object. 

1278 

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) 

1288 

1289 return cls( 

1290 crl_distribution_points=crl_distribution_points, 

1291 _x509_obj=extension, 

1292 ) 

1293 

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()) 

1298 

1299 return ret 

1300 

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()) 

1305 

1306 return x509.CRLDistributionPoints(dist_points) 

1307 

1308 

1309class IssuingDistributionPoint(Extension): 

1310 """ 

1311 Represents the Issuing Distribution Points extension in X.509 certificates. 

1312 

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 """ 

1317 

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 

1325 

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. 

1334 

1335 Args: 

1336 extension: The cryptography IssuingDistributionPoint object. 

1337 

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)) 

1347 

1348 relative_name = None 

1349 if extension.relative_name is not None: 

1350 relative_name = RelativeDistinguishedName.from_cryptography( 

1351 extension.relative_name 

1352 ) 

1353 

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)) 

1359 

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 ) 

1370 

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 

1387 

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()) 

1394 

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 ) 

1400 

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) 

1407 

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 ) 

1417 

1418 

1419class InhibitAnyPolicy(Extension): 

1420 """ 

1421 Represents the Inhibit Any Policy extension in X.509 certificates. 

1422 

1423 Attributes: 

1424 skip_certs: Number of certificates to skip. 

1425 """ 

1426 

1427 skip_certs: int 

1428 

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. 

1436 

1437 Args: 

1438 extension: The cryptography InhibitAnyPolicy object. 

1439 

1440 Returns: 

1441 The constructed InhibitAnyPolicy object. 

1442 """ 

1443 return cls( 

1444 skip_certs=extension.skip_certs, 

1445 _x509_obj=extension, 

1446 ) 

1447 

1448 def _string_dict(self): 

1449 return {self.name: {"Skip Certs": self.skip_certs}} 

1450 

1451 def _to_cryptography(self) -> x509.InhibitAnyPolicy: 

1452 return x509.InhibitAnyPolicy(self.skip_certs) 

1453 

1454 

1455class FreshestCrl(CrlDistributionPoints): 

1456 """ 

1457 Represents the Freshest CRL extension in X.509 certificates. 

1458 """ 

1459 

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()) 

1464 

1465 return x509.FreshestCRL(dist_points) 

1466 

1467 

1468class AccessDescriptionId(Enum): 

1469 """ 

1470 Enumeration of access description identifiers for X.509 certificates. 

1471 """ 

1472 

1473 CA_ISSUERS = "1.3.6.1.5.5.7.48.2" 

1474 OCSP = "1.3.6.1.5.5.7.48.1" 

1475 

1476 

1477class AccessDescription(CryptoParser): 

1478 """ 

1479 Represents an Access Description in X.509 certificates. 

1480 

1481 Attributes: 

1482 access_method: The access method identifier. 

1483 access_location: The access location. 

1484 """ 

1485 

1486 access_method: AccessDescriptionId 

1487 access_location: GeneralName 

1488 

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. 

1496 

1497 Args: 

1498 extension: The cryptography AccessDescription object. 

1499 

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 ) 

1513 

1514 def _string_dict(self): 

1515 return { 

1516 "Access Method": self.access_method.name, 

1517 "Access Location": self.access_location._string_dict(), 

1518 } 

1519 

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 ) 

1527 

1528 

1529class AuthorityInformationAccess(Extension): 

1530 """ 

1531 Represents the Authority Information Access extension in X.509 

1532 certificates. 

1533 

1534 Attributes: 

1535 access_description: List of access descriptions. 

1536 """ 

1537 

1538 access_description: list[AccessDescription] 

1539 

1540 def __iter__(self) -> Iterator[AccessDescription]: 

1541 return iter(self.access_description) 

1542 

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. 

1551 

1552 Args: 

1553 extension: The cryptography AuthorityInformationAccess object. 

1554 

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 ) 

1567 

1568 def _string_dict(self): 

1569 access_descriptions = [] 

1570 for access_description in self.access_description: 

1571 access_descriptions.append(access_description._string_dict()) 

1572 

1573 return {self.name: {"Access Description": access_descriptions}} 

1574 

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) 

1580 

1581 

1582class SubjectInformationAccess(AuthorityInformationAccess): 

1583 """ 

1584 Represents the Subject Information Access extension in X.509 

1585 certificates. 

1586 """ 

1587 

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) 

1593 

1594 

1595class Extensions(CryptoParser): 

1596 """ 

1597 Extensions in X.509 certificates. 

1598 

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 """ 

1618 

1619 model_config = ConfigDict(populate_by_name=True) 

1620 

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 ) 

1684 

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 

1690 

1691 yield getattr(self, field_name) 

1692 

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. 

1699 

1700 Args: 

1701 cert_extensions: The cryptography X.509 Extensions. 

1702 

1703 Returns: 

1704 The constructed Extensions object. 

1705 """ 

1706 extensions_dict = {"_x509_obj": cert_extensions} 

1707 

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) 

1713 

1714 ext = class_type.from_cryptography(x509_ext.value) 

1715 

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") 

1720 

1721 return cls(**extensions_dict) 

1722 

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 

1733 

1734 def _string_dict(self): 

1735 extensions = {} 

1736 for field_name in Extensions.model_fields: 

1737 att_val = getattr(self, field_name) 

1738 

1739 if att_val is None or str(att_val) == "": 

1740 continue 

1741 

1742 extensions.update(att_val._string_dict()) 

1743 

1744 return extensions 

1745 

1746 def _to_cryptography(self) -> x509.Extensions: 

1747 extensions = [] 

1748 for field_name in Extensions.model_fields: 

1749 att_val = getattr(self, field_name) 

1750 

1751 if att_val is None or str(att_val) == "": 

1752 continue 

1753 

1754 extensions.append(att_val._to_cryptography()) 

1755 

1756 return x509.Extensions(extensions)