You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

536 lines
16 KiB

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