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