1# coding: utf-8 2 3""" 4ASN.1 type classes for certificate revocation lists (CRL). Exports the 5following items: 6 7 - CertificateList() 8 9Other type classes are defined that help compose the types listed above. 10""" 11 12from __future__ import unicode_literals, division, absolute_import, print_function 13 14import hashlib 15 16from .algos import SignedDigestAlgorithm 17from .core import ( 18 Boolean, 19 Enumerated, 20 GeneralizedTime, 21 Integer, 22 ObjectIdentifier, 23 OctetBitString, 24 ParsableOctetString, 25 Sequence, 26 SequenceOf, 27) 28from .x509 import ( 29 AuthorityInfoAccessSyntax, 30 AuthorityKeyIdentifier, 31 CRLDistributionPoints, 32 DistributionPointName, 33 GeneralNames, 34 Name, 35 ReasonFlags, 36 Time, 37) 38 39 40# The structures in this file are taken from https://tools.ietf.org/html/rfc5280 41 42 43class Version(Integer): 44 _map = { 45 0: 'v1', 46 1: 'v2', 47 2: 'v3', 48 } 49 50 51class IssuingDistributionPoint(Sequence): 52 _fields = [ 53 ('distribution_point', DistributionPointName, {'explicit': 0, 'optional': True}), 54 ('only_contains_user_certs', Boolean, {'implicit': 1, 'default': False}), 55 ('only_contains_ca_certs', Boolean, {'implicit': 2, 'default': False}), 56 ('only_some_reasons', ReasonFlags, {'implicit': 3, 'optional': True}), 57 ('indirect_crl', Boolean, {'implicit': 4, 'default': False}), 58 ('only_contains_attribute_certs', Boolean, {'implicit': 5, 'default': False}), 59 ] 60 61 62class TBSCertListExtensionId(ObjectIdentifier): 63 _map = { 64 '2.5.29.18': 'issuer_alt_name', 65 '2.5.29.20': 'crl_number', 66 '2.5.29.27': 'delta_crl_indicator', 67 '2.5.29.28': 'issuing_distribution_point', 68 '2.5.29.35': 'authority_key_identifier', 69 '2.5.29.46': 'freshest_crl', 70 '1.3.6.1.5.5.7.1.1': 'authority_information_access', 71 } 72 73 74class TBSCertListExtension(Sequence): 75 _fields = [ 76 ('extn_id', TBSCertListExtensionId), 77 ('critical', Boolean, {'default': False}), 78 ('extn_value', ParsableOctetString), 79 ] 80 81 _oid_pair = ('extn_id', 'extn_value') 82 _oid_specs = { 83 'issuer_alt_name': GeneralNames, 84 'crl_number': Integer, 85 'delta_crl_indicator': Integer, 86 'issuing_distribution_point': IssuingDistributionPoint, 87 'authority_key_identifier': AuthorityKeyIdentifier, 88 'freshest_crl': CRLDistributionPoints, 89 'authority_information_access': AuthorityInfoAccessSyntax, 90 } 91 92 93class TBSCertListExtensions(SequenceOf): 94 _child_spec = TBSCertListExtension 95 96 97class CRLReason(Enumerated): 98 _map = { 99 0: 'unspecified', 100 1: 'key_compromise', 101 2: 'ca_compromise', 102 3: 'affiliation_changed', 103 4: 'superseded', 104 5: 'cessation_of_operation', 105 6: 'certificate_hold', 106 8: 'remove_from_crl', 107 9: 'privilege_withdrawn', 108 10: 'aa_compromise', 109 } 110 111 @property 112 def human_friendly(self): 113 """ 114 :return: 115 A unicode string with revocation description that is suitable to 116 show to end-users. Starts with a lower case letter and phrased in 117 such a way that it makes sense after the phrase "because of" or 118 "due to". 119 """ 120 121 return { 122 'unspecified': 'an unspecified reason', 123 'key_compromise': 'a compromised key', 124 'ca_compromise': 'the CA being compromised', 125 'affiliation_changed': 'an affiliation change', 126 'superseded': 'certificate supersession', 127 'cessation_of_operation': 'a cessation of operation', 128 'certificate_hold': 'a certificate hold', 129 'remove_from_crl': 'removal from the CRL', 130 'privilege_withdrawn': 'privilege withdrawl', 131 'aa_compromise': 'the AA being compromised', 132 }[self.native] 133 134 135class CRLEntryExtensionId(ObjectIdentifier): 136 _map = { 137 '2.5.29.21': 'crl_reason', 138 '2.5.29.23': 'hold_instruction_code', 139 '2.5.29.24': 'invalidity_date', 140 '2.5.29.29': 'certificate_issuer', 141 } 142 143 144class CRLEntryExtension(Sequence): 145 _fields = [ 146 ('extn_id', CRLEntryExtensionId), 147 ('critical', Boolean, {'default': False}), 148 ('extn_value', ParsableOctetString), 149 ] 150 151 _oid_pair = ('extn_id', 'extn_value') 152 _oid_specs = { 153 'crl_reason': CRLReason, 154 'hold_instruction_code': ObjectIdentifier, 155 'invalidity_date': GeneralizedTime, 156 'certificate_issuer': GeneralNames, 157 } 158 159 160class CRLEntryExtensions(SequenceOf): 161 _child_spec = CRLEntryExtension 162 163 164class RevokedCertificate(Sequence): 165 _fields = [ 166 ('user_certificate', Integer), 167 ('revocation_date', Time), 168 ('crl_entry_extensions', CRLEntryExtensions, {'optional': True}), 169 ] 170 171 _processed_extensions = False 172 _critical_extensions = None 173 _crl_reason_value = None 174 _invalidity_date_value = None 175 _certificate_issuer_value = None 176 _issuer_name = False 177 178 def _set_extensions(self): 179 """ 180 Sets common named extensions to private attributes and creates a list 181 of critical extensions 182 """ 183 184 self._critical_extensions = set() 185 186 for extension in self['crl_entry_extensions']: 187 name = extension['extn_id'].native 188 attribute_name = '_%s_value' % name 189 if hasattr(self, attribute_name): 190 setattr(self, attribute_name, extension['extn_value'].parsed) 191 if extension['critical'].native: 192 self._critical_extensions.add(name) 193 194 self._processed_extensions = True 195 196 @property 197 def critical_extensions(self): 198 """ 199 Returns a set of the names (or OID if not a known extension) of the 200 extensions marked as critical 201 202 :return: 203 A set of unicode strings 204 """ 205 206 if not self._processed_extensions: 207 self._set_extensions() 208 return self._critical_extensions 209 210 @property 211 def crl_reason_value(self): 212 """ 213 This extension indicates the reason that a certificate was revoked. 214 215 :return: 216 None or a CRLReason object 217 """ 218 219 if self._processed_extensions is False: 220 self._set_extensions() 221 return self._crl_reason_value 222 223 @property 224 def invalidity_date_value(self): 225 """ 226 This extension indicates the suspected date/time the private key was 227 compromised or the certificate became invalid. This would usually be 228 before the revocation date, which is when the CA processed the 229 revocation. 230 231 :return: 232 None or a GeneralizedTime object 233 """ 234 235 if self._processed_extensions is False: 236 self._set_extensions() 237 return self._invalidity_date_value 238 239 @property 240 def certificate_issuer_value(self): 241 """ 242 This extension indicates the issuer of the certificate in question, 243 and is used in indirect CRLs. CRL entries without this extension are 244 for certificates issued from the last seen issuer. 245 246 :return: 247 None or an x509.GeneralNames object 248 """ 249 250 if self._processed_extensions is False: 251 self._set_extensions() 252 return self._certificate_issuer_value 253 254 @property 255 def issuer_name(self): 256 """ 257 :return: 258 None, or an asn1crypto.x509.Name object for the issuer of the cert 259 """ 260 261 if self._issuer_name is False: 262 self._issuer_name = None 263 if self.certificate_issuer_value: 264 for general_name in self.certificate_issuer_value: 265 if general_name.name == 'directory_name': 266 self._issuer_name = general_name.chosen 267 break 268 return self._issuer_name 269 270 271class RevokedCertificates(SequenceOf): 272 _child_spec = RevokedCertificate 273 274 275class TbsCertList(Sequence): 276 _fields = [ 277 ('version', Version, {'optional': True}), 278 ('signature', SignedDigestAlgorithm), 279 ('issuer', Name), 280 ('this_update', Time), 281 ('next_update', Time, {'optional': True}), 282 ('revoked_certificates', RevokedCertificates, {'optional': True}), 283 ('crl_extensions', TBSCertListExtensions, {'explicit': 0, 'optional': True}), 284 ] 285 286 287class CertificateList(Sequence): 288 _fields = [ 289 ('tbs_cert_list', TbsCertList), 290 ('signature_algorithm', SignedDigestAlgorithm), 291 ('signature', OctetBitString), 292 ] 293 294 _processed_extensions = False 295 _critical_extensions = None 296 _issuer_alt_name_value = None 297 _crl_number_value = None 298 _delta_crl_indicator_value = None 299 _issuing_distribution_point_value = None 300 _authority_key_identifier_value = None 301 _freshest_crl_value = None 302 _authority_information_access_value = None 303 _issuer_cert_urls = None 304 _delta_crl_distribution_points = None 305 _sha1 = None 306 _sha256 = None 307 308 def _set_extensions(self): 309 """ 310 Sets common named extensions to private attributes and creates a list 311 of critical extensions 312 """ 313 314 self._critical_extensions = set() 315 316 for extension in self['tbs_cert_list']['crl_extensions']: 317 name = extension['extn_id'].native 318 attribute_name = '_%s_value' % name 319 if hasattr(self, attribute_name): 320 setattr(self, attribute_name, extension['extn_value'].parsed) 321 if extension['critical'].native: 322 self._critical_extensions.add(name) 323 324 self._processed_extensions = True 325 326 @property 327 def critical_extensions(self): 328 """ 329 Returns a set of the names (or OID if not a known extension) of the 330 extensions marked as critical 331 332 :return: 333 A set of unicode strings 334 """ 335 336 if not self._processed_extensions: 337 self._set_extensions() 338 return self._critical_extensions 339 340 @property 341 def issuer_alt_name_value(self): 342 """ 343 This extension allows associating one or more alternative names with 344 the issuer of the CRL. 345 346 :return: 347 None or an x509.GeneralNames object 348 """ 349 350 if self._processed_extensions is False: 351 self._set_extensions() 352 return self._issuer_alt_name_value 353 354 @property 355 def crl_number_value(self): 356 """ 357 This extension adds a monotonically increasing number to the CRL and is 358 used to distinguish different versions of the CRL. 359 360 :return: 361 None or an Integer object 362 """ 363 364 if self._processed_extensions is False: 365 self._set_extensions() 366 return self._crl_number_value 367 368 @property 369 def delta_crl_indicator_value(self): 370 """ 371 This extension indicates a CRL is a delta CRL, and contains the CRL 372 number of the base CRL that it is a delta from. 373 374 :return: 375 None or an Integer object 376 """ 377 378 if self._processed_extensions is False: 379 self._set_extensions() 380 return self._delta_crl_indicator_value 381 382 @property 383 def issuing_distribution_point_value(self): 384 """ 385 This extension includes information about what types of revocations 386 and certificates are part of the CRL. 387 388 :return: 389 None or an IssuingDistributionPoint object 390 """ 391 392 if self._processed_extensions is False: 393 self._set_extensions() 394 return self._issuing_distribution_point_value 395 396 @property 397 def authority_key_identifier_value(self): 398 """ 399 This extension helps in identifying the public key with which to 400 validate the authenticity of the CRL. 401 402 :return: 403 None or an AuthorityKeyIdentifier object 404 """ 405 406 if self._processed_extensions is False: 407 self._set_extensions() 408 return self._authority_key_identifier_value 409 410 @property 411 def freshest_crl_value(self): 412 """ 413 This extension is used in complete CRLs to indicate where a delta CRL 414 may be located. 415 416 :return: 417 None or a CRLDistributionPoints object 418 """ 419 420 if self._processed_extensions is False: 421 self._set_extensions() 422 return self._freshest_crl_value 423 424 @property 425 def authority_information_access_value(self): 426 """ 427 This extension is used to provide a URL with which to download the 428 certificate used to sign this CRL. 429 430 :return: 431 None or an AuthorityInfoAccessSyntax object 432 """ 433 434 if self._processed_extensions is False: 435 self._set_extensions() 436 return self._authority_information_access_value 437 438 @property 439 def issuer(self): 440 """ 441 :return: 442 An asn1crypto.x509.Name object for the issuer of the CRL 443 """ 444 445 return self['tbs_cert_list']['issuer'] 446 447 @property 448 def authority_key_identifier(self): 449 """ 450 :return: 451 None or a byte string of the key_identifier from the authority key 452 identifier extension 453 """ 454 455 if not self.authority_key_identifier_value: 456 return None 457 458 return self.authority_key_identifier_value['key_identifier'].native 459 460 @property 461 def issuer_cert_urls(self): 462 """ 463 :return: 464 A list of unicode strings that are URLs that should contain either 465 an individual DER-encoded X.509 certificate, or a DER-encoded CMS 466 message containing multiple certificates 467 """ 468 469 if self._issuer_cert_urls is None: 470 self._issuer_cert_urls = [] 471 if self.authority_information_access_value: 472 for entry in self.authority_information_access_value: 473 if entry['access_method'].native == 'ca_issuers': 474 location = entry['access_location'] 475 if location.name != 'uniform_resource_identifier': 476 continue 477 url = location.native 478 if url.lower()[0:7] == 'http://': 479 self._issuer_cert_urls.append(url) 480 return self._issuer_cert_urls 481 482 @property 483 def delta_crl_distribution_points(self): 484 """ 485 Returns delta CRL URLs - only applies to complete CRLs 486 487 :return: 488 A list of zero or more DistributionPoint objects 489 """ 490 491 if self._delta_crl_distribution_points is None: 492 self._delta_crl_distribution_points = [] 493 494 if self.freshest_crl_value is not None: 495 for distribution_point in self.freshest_crl_value: 496 distribution_point_name = distribution_point['distribution_point'] 497 # RFC 5280 indicates conforming CA should not use the relative form 498 if distribution_point_name.name == 'name_relative_to_crl_issuer': 499 continue 500 # This library is currently only concerned with HTTP-based CRLs 501 for general_name in distribution_point_name.chosen: 502 if general_name.name == 'uniform_resource_identifier': 503 self._delta_crl_distribution_points.append(distribution_point) 504 505 return self._delta_crl_distribution_points 506 507 @property 508 def signature(self): 509 """ 510 :return: 511 A byte string of the signature 512 """ 513 514 return self['signature'].native 515 516 @property 517 def sha1(self): 518 """ 519 :return: 520 The SHA1 hash of the DER-encoded bytes of this certificate list 521 """ 522 523 if self._sha1 is None: 524 self._sha1 = hashlib.sha1(self.dump()).digest() 525 return self._sha1 526 527 @property 528 def sha256(self): 529 """ 530 :return: 531 The SHA-256 hash of the DER-encoded bytes of this certificate list 532 """ 533 534 if self._sha256 is None: 535 self._sha256 = hashlib.sha256(self.dump()).digest() 536 return self._sha256 537