File: __init__.py

package info (click to toggle)
python-certvalidator 0.11.1-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,488 kB
  • sloc: python: 6,740; makefile: 8
file content (224 lines) | stat: -rw-r--r-- 7,969 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function

from asn1crypto import pem
from asn1crypto.x509 import Certificate

from .context import ValidationContext
from .errors import ValidationError, PathBuildingError, InvalidCertificateError
from .validate import validate_path, validate_tls_hostname, validate_usage
from ._errors import pretty_message
from ._types import type_name, str_cls, byte_cls
from ._version import __version__, __version_info__


__all__ = [
    '__version__',
    '__version_info__',
    'CertificateValidator',
    'ValidationContext',
]


class CertificateValidator():

    # A certvalidator.context.ValidationContext object
    _context = None

    # An asn1crypto.x509.Certificate object
    _certificate = None

    # A certvalidator.path.ValidationPath object - only set once validated
    _path = None

    def __init__(self, end_entity_cert, intermediate_certs=None, validation_context=None):
        """
        :param end_entity_cert:
            An asn1crypto.x509.Certificate object or a byte string of the DER or
            PEM-encoded X.509 end-entity certificate to validate

        :param intermediate_certs:
            None or a list of asn1crypto.x509.Certificate objects or a byte
            string of a DER or PEM-encoded X.509 certificate. Used in
            constructing certificate paths for validation.

        :param validation_context:
            A certvalidator.context.ValidationContext() object that controls
            validation options
        """

        if not isinstance(end_entity_cert, Certificate):
            if not isinstance(end_entity_cert, byte_cls):
                raise TypeError(pretty_message(
                    '''
                    end_entity_cert must be a byte string or an instance of
                    asn1crypto.x509.Certificate, not %s
                    ''',
                    type_name(end_entity_cert)
                ))
            if pem.detect(end_entity_cert):
                _, _, end_entity_cert = pem.unarmor(end_entity_cert)
            end_entity_cert = Certificate.load(end_entity_cert)

        if validation_context is None:
            validation_context = ValidationContext()

        if not isinstance(validation_context, ValidationContext):
            raise TypeError(pretty_message(
                '''
                validation_context must be an instance of
                certvalidator.context.ValidationContext, not %s
                ''',
                type_name(validation_context)
            ))

        if intermediate_certs is not None:
            certificate_registry = validation_context.certificate_registry
            for intermediate_cert in intermediate_certs:
                certificate_registry.add_other_cert(intermediate_cert)

        self._context = validation_context
        self._certificate = end_entity_cert

    def _validate_path(self):
        """
        Builds possible certificate paths and validates them until a valid one
        is found, or all fail.

        :raises:
            certvalidator.errors.PathValidationError - when an error occurs validating the path
            certvalidator.errors.RevokedError - when the certificate or another certificate in its path has been revoked
        """

        if self._path is not None:
            return

        exceptions = []

        if self._certificate.hash_algo in self._context.weak_hash_algos:
            raise InvalidCertificateError(pretty_message(
                '''
                The X.509 certificate provided has a signature using the weak
                hash algorithm %s
                ''',
                self._certificate.hash_algo
            ))

        try:
            paths = self._context.certificate_registry.build_paths(self._certificate)
        except (PathBuildingError) as e:
            if self._certificate.self_signed in set(['yes', 'maybe']):
                raise InvalidCertificateError(pretty_message(
                    '''
                    The X.509 certificate provided is self-signed - "%s"
                    ''',
                    self._certificate.subject.human_friendly
                ))
            raise

        for candidate_path in paths:
            try:
                validate_path(self._context, candidate_path)
                self._path = candidate_path
                return
            except (ValidationError) as e:
                exceptions.append(e)

        if len(exceptions) == 1:
            raise exceptions[0]

        non_signature_exception = None
        for exception in exceptions:
            if 'signature' not in str_cls(exception):
                non_signature_exception = exception

        if non_signature_exception:
            raise non_signature_exception

        raise exceptions[0]

    def validate_usage(self, key_usage, extended_key_usage=None, extended_optional=False):
        """
        Validates the certificate path and that the certificate is valid for
        the key usage and extended key usage purposes specified.

        :param key_usage:
            A set of unicode strings of the required key usage purposes. Valid
            values include:

             - "digital_signature"
             - "non_repudiation"
             - "key_encipherment"
             - "data_encipherment"
             - "key_agreement"
             - "key_cert_sign"
             - "crl_sign"
             - "encipher_only"
             - "decipher_only"

        :param extended_key_usage:
            A set of unicode strings of the required extended key usage
            purposes. These must be either dotted number OIDs, or one of the
            following extended key usage purposes:

             - "server_auth"
             - "client_auth"
             - "code_signing"
             - "email_protection"
             - "ipsec_end_system"
             - "ipsec_tunnel"
             - "ipsec_user"
             - "time_stamping"
             - "ocsp_signing"
             - "wireless_access_points"

            An example of a dotted number OID:

             - "1.3.6.1.5.5.7.3.1"

        :param extended_optional:
            A bool - if the extended_key_usage extension may be ommited and still
            considered valid

        :raises:
            certvalidator.errors.PathValidationError - when an error occurs validating the path
            certvalidator.errors.RevokedError - when the certificate or another certificate in its path has been revoked
            certvalidator.errors.InvalidCertificateError - when the certificate is not valid for the usages specified

        :return:
            A certvalidator.path.ValidationPath object of the validated
            certificate validation path
        """

        self._validate_path()
        validate_usage(
            self._context,
            self._certificate,
            key_usage,
            extended_key_usage,
            extended_optional
        )
        return self._path

    def validate_tls(self, hostname):
        """
        Validates the certificate path, that the certificate is valid for
        the hostname provided and that the certificate is valid for the purpose
        of a TLS connection.

        :param hostname:
            A unicode string of the TLS server hostname

        :raises:
            certvalidator.errors.PathValidationError - when an error occurs validating the path
            certvalidator.errors.RevokedError - when the certificate or another certificate in its path has been revoked
            certvalidator.errors.InvalidCertificateError - when the certificate is not valid for TLS or the hostname

        :return:
            A certvalidator.path.ValidationPath object of the validated
            certificate validation path
        """

        self._validate_path()
        validate_tls_hostname(self._context, self._certificate, hostname)
        return self._path