diff options
| author | Paul Kehrer <paul.l.kehrer@gmail.com> | 2015-10-21 20:17:15 -0500 | 
|---|---|---|
| committer | Paul Kehrer <paul.l.kehrer@gmail.com> | 2015-10-21 20:17:15 -0500 | 
| commit | 6a2e08bcf0cd8fddb0562d9a9d6864be8d2a0ba1 (patch) | |
| tree | 5dae60bc8cb3f12fe876fcf0c6a6c84f5ea39e2f /src | |
| parent | 41cfb0d634fac246cd7634913c36598f5247b559 (diff) | |
| parent | cee79f88f8d8a1758042234e1cb8dcb0c07c11d4 (diff) | |
| download | cryptography-6a2e08bcf0cd8fddb0562d9a9d6864be8d2a0ba1.tar.gz cryptography-6a2e08bcf0cd8fddb0562d9a9d6864be8d2a0ba1.tar.bz2 cryptography-6a2e08bcf0cd8fddb0562d9a9d6864be8d2a0ba1.zip | |
Merge pull request #2315 from etrauschke/crl_ossl_backend
OpenSSL backend code for CRLs
Diffstat (limited to 'src')
| -rw-r--r-- | src/cryptography/hazmat/backends/multibackend.py | 18 | ||||
| -rw-r--r-- | src/cryptography/hazmat/backends/openssl/backend.py | 26 | ||||
| -rw-r--r-- | src/cryptography/hazmat/backends/openssl/x509.py | 221 | ||||
| -rw-r--r-- | src/cryptography/x509/__init__.py | 6 | ||||
| -rw-r--r-- | src/cryptography/x509/base.py | 14 | 
5 files changed, 263 insertions, 22 deletions
| diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py index c4d2c133..bbaaf424 100644 --- a/src/cryptography/hazmat/backends/multibackend.py +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -331,6 +331,24 @@ class MultiBackend(object):              _Reasons.UNSUPPORTED_X509          ) +    def load_pem_x509_crl(self, data): +        for b in self._filtered_backends(X509Backend): +            return b.load_pem_x509_crl(data) + +        raise UnsupportedAlgorithm( +            "This backend does not support X.509.", +            _Reasons.UNSUPPORTED_X509 +        ) + +    def load_der_x509_crl(self, data): +        for b in self._filtered_backends(X509Backend): +            return b.load_der_x509_crl(data) + +        raise UnsupportedAlgorithm( +            "This backend does not support X.509.", +            _Reasons.UNSUPPORTED_X509 +        ) +      def load_der_x509_csr(self, data):          for b in self._filtered_backends(X509Backend):              return b.load_der_x509_csr(data) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index f86c3aa1..58587b94 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -37,8 +37,8 @@ from cryptography.hazmat.backends.openssl.rsa import (      _RSAPrivateKey, _RSAPublicKey  )  from cryptography.hazmat.backends.openssl.x509 import ( -    _Certificate, _CertificateSigningRequest, _DISTPOINT_TYPE_FULLNAME, -    _DISTPOINT_TYPE_RELATIVENAME +    _Certificate, _CertificateRevocationList, _CertificateSigningRequest, +    _DISTPOINT_TYPE_FULLNAME, _DISTPOINT_TYPE_RELATIVENAME  )  from cryptography.hazmat.bindings.openssl import binding  from cryptography.hazmat.primitives import hashes, serialization @@ -1452,6 +1452,28 @@ class Backend(object):          x509 = self._ffi.gc(x509, self._lib.X509_free)          return _Certificate(self, x509) +    def load_pem_x509_crl(self, data): +        mem_bio = self._bytes_to_bio(data) +        x509_crl = self._lib.PEM_read_bio_X509_CRL( +            mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL +        ) +        if x509_crl == self._ffi.NULL: +            self._consume_errors() +            raise ValueError("Unable to load CRL") + +        x509_crl = self._ffi.gc(x509_crl, self._lib.X509_CRL_free) +        return _CertificateRevocationList(self, x509_crl) + +    def load_der_x509_crl(self, data): +        mem_bio = self._bytes_to_bio(data) +        x509_crl = self._lib.d2i_X509_CRL_bio(mem_bio.bio, self._ffi.NULL) +        if x509_crl == self._ffi.NULL: +            self._consume_errors() +            raise ValueError("Unable to load CRL") + +        x509_crl = self._ffi.gc(x509_crl, self._lib.X509_CRL_free) +        return _CertificateRevocationList(self, x509_crl) +      def load_pem_x509_csr(self, data):          mem_bio = self._bytes_to_bio(data)          x509_req = self._lib.PEM_read_bio_X509_REQ( diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index 2de5a8c7..2790ec7d 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -4,7 +4,9 @@  from __future__ import absolute_import, division, print_function +import datetime  import ipaddress +  from email.utils import parseaddr  import idna @@ -16,7 +18,9 @@ from six.moves import urllib_parse  from cryptography import utils, x509  from cryptography.exceptions import UnsupportedAlgorithm  from cryptography.hazmat.primitives import hashes, serialization -from cryptography.x509.oid import CertificatePoliciesOID, ExtensionOID +from cryptography.x509.oid import ( +    CRLExtensionOID, CertificatePoliciesOID, ExtensionOID +)  def _obj2txt(backend, obj): @@ -176,10 +180,11 @@ def _decode_ocsp_no_check(backend, ext):  class _X509ExtensionParser(object): -    def __init__(self, ext_count, get_ext, handlers): +    def __init__(self, ext_count, get_ext, handlers, unsupported_exts=None):          self.ext_count = ext_count          self.get_ext = get_ext          self.handlers = handlers +        self.unsupported_exts = unsupported_exts      def parse(self, backend, x509_obj):          extensions = [] @@ -199,18 +204,25 @@ class _X509ExtensionParser(object):              except KeyError:                  if critical:                      raise x509.UnsupportedExtension( -                        "{0} is not currently supported".format(oid), oid +                        "Critical extension {0} is not currently supported" +                        .format(oid), oid                      )              else: -                d2i = backend._lib.X509V3_EXT_d2i(ext) -                if d2i == backend._ffi.NULL: -                    backend._consume_errors() -                    raise ValueError( -                        "The {0} extension is invalid and can't be " -                        "parsed".format(oid) -                    ) - -                value = handler(backend, d2i) +                # For extensions which are not supported by OpenSSL we pass the +                # extension object directly to the parsing routine so it can +                # be decoded manually. +                if self.unsupported_exts and oid in self.unsupported_exts: +                    ext_data = ext +                else: +                    ext_data = backend._lib.X509V3_EXT_d2i(ext) +                    if ext_data == backend._ffi.NULL: +                        backend._consume_errors() +                        raise ValueError( +                            "The {0} extension is invalid and can't be " +                            "parsed".format(oid) +                        ) + +                value = handler(backend, ext_data)                  extensions.append(x509.Extension(oid, critical, value))              seen_oids.add(oid) @@ -646,6 +658,175 @@ def _decode_inhibit_any_policy(backend, asn1_int):      return x509.InhibitAnyPolicy(skip_certs) +def _decode_crl_reason(backend, enum): +    enum = backend._ffi.cast("ASN1_ENUMERATED *", enum) +    enum = backend._ffi.gc(enum, backend._lib.ASN1_ENUMERATED_free) +    code = backend._lib.ASN1_ENUMERATED_get(enum) + +    try: +        return { +            0: x509.ReasonFlags.unspecified, +            1: x509.ReasonFlags.key_compromise, +            2: x509.ReasonFlags.ca_compromise, +            3: x509.ReasonFlags.affiliation_changed, +            4: x509.ReasonFlags.superseded, +            5: x509.ReasonFlags.cessation_of_operation, +            6: x509.ReasonFlags.certificate_hold, +            8: x509.ReasonFlags.remove_from_crl, +            9: x509.ReasonFlags.privilege_withdrawn, +            10: x509.ReasonFlags.aa_compromise, +        }[code] +    except KeyError: +        raise ValueError("Unsupported reason code: {0}".format(code)) + + +def _decode_invalidity_date(backend, inv_date): +    generalized_time = backend._ffi.cast( +        "ASN1_GENERALIZEDTIME *", inv_date +    ) +    generalized_time = backend._ffi.gc( +        generalized_time, backend._lib.ASN1_GENERALIZEDTIME_free +    ) +    time = backend._ffi.string( +        backend._lib.ASN1_STRING_data( +            backend._ffi.cast("ASN1_STRING *", generalized_time) +        ) +    ).decode("ascii") +    return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ") + + +def _decode_cert_issuer(backend, ext): +    """ +    This handler decodes the CertificateIssuer entry extension directly +    from the X509_EXTENSION object. This is necessary because this entry +    extension is not directly supported by OpenSSL 0.9.8. +    """ + +    data_ptr_ptr = backend._ffi.new("const unsigned char **") +    data_ptr_ptr[0] = ext.value.data +    gns = backend._lib.d2i_GENERAL_NAMES( +        backend._ffi.NULL, data_ptr_ptr, ext.value.length +    ) + +    # Check the result of d2i_GENERAL_NAMES() is valid. Usually this is covered +    # in _X509ExtensionParser but since we are responsible for decoding this +    # entry extension ourselves, we have to this here. +    if gns == backend._ffi.NULL: +        backend._consume_errors() +        raise ValueError( +            "The {0} extension is corrupted and can't be parsed".format( +                CRLExtensionOID.CERTIFICATE_ISSUER)) + +    gns = backend._ffi.gc(gns, backend._lib.GENERAL_NAMES_free) +    return x509.GeneralNames(_decode_general_names(backend, gns)) + + +@utils.register_interface(x509.RevokedCertificate) +class _RevokedCertificate(object): +    def __init__(self, backend, x509_revoked): +        self._backend = backend +        self._x509_revoked = x509_revoked + +    @property +    def serial_number(self): +        asn1_int = self._x509_revoked.serialNumber +        self._backend.openssl_assert(asn1_int != self._backend._ffi.NULL) +        return self._backend._asn1_integer_to_int(asn1_int) + +    @property +    def revocation_date(self): +        return self._backend._parse_asn1_time( +            self._x509_revoked.revocationDate) + +    @property +    def extensions(self): +        return _REVOKED_CERTIFICATE_EXTENSION_PARSER.parse( +            self._backend, self._x509_revoked +        ) + + +@utils.register_interface(x509.CertificateRevocationList) +class _CertificateRevocationList(object): +    def __init__(self, backend, x509_crl): +        self._backend = backend +        self._x509_crl = x509_crl + +    def __eq__(self, other): +        if not isinstance(other, x509.CertificateRevocationList): +            return NotImplemented + +        res = self._backend._lib.X509_CRL_cmp(self._x509_crl, other._x509_crl) +        return res == 0 + +    def __ne__(self, other): +        return not self == other + +    def fingerprint(self, algorithm): +        h = hashes.Hash(algorithm, self._backend) +        bio = self._backend._create_mem_bio() +        res = self._backend._lib.i2d_X509_CRL_bio( +            bio, self._x509_crl +        ) +        self._backend.openssl_assert(res == 1) +        der = self._backend._read_mem_bio(bio) +        h.update(der) +        return h.finalize() + +    @property +    def signature_hash_algorithm(self): +        oid = _obj2txt(self._backend, self._x509_crl.sig_alg.algorithm) +        try: +            return x509._SIG_OIDS_TO_HASH[oid] +        except KeyError: +            raise UnsupportedAlgorithm( +                "Signature algorithm OID:{0} not recognized".format(oid) +            ) + +    @property +    def issuer(self): +        issuer = self._backend._lib.X509_CRL_get_issuer(self._x509_crl) +        self._backend.openssl_assert(issuer != self._backend._ffi.NULL) +        return _decode_x509_name(self._backend, issuer) + +    @property +    def next_update(self): +        nu = self._backend._lib.X509_CRL_get_nextUpdate(self._x509_crl) +        self._backend.openssl_assert(nu != self._backend._ffi.NULL) +        return self._backend._parse_asn1_time(nu) + +    @property +    def last_update(self): +        lu = self._backend._lib.X509_CRL_get_lastUpdate(self._x509_crl) +        self._backend.openssl_assert(lu != self._backend._ffi.NULL) +        return self._backend._parse_asn1_time(lu) + +    def _revoked_certificates(self): +        revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl) +        self._backend.openssl_assert(revoked != self._backend._ffi.NULL) + +        num = self._backend._lib.sk_X509_REVOKED_num(revoked) +        revoked_list = [] +        for i in range(num): +            r = self._backend._lib.sk_X509_REVOKED_value(revoked, i) +            self._backend.openssl_assert(r != self._backend._ffi.NULL) +            revoked_list.append(_RevokedCertificate(self._backend, r)) + +        return revoked_list + +    def __iter__(self): +        return iter(self._revoked_certificates()) + +    def __getitem__(self, idx): +        return self._revoked_certificates()[idx] + +    def __len__(self): +        return len(self._revoked_certificates()) + +    @property +    def extensions(self): +        raise NotImplementedError() + +  @utils.register_interface(x509.CertificateSigningRequest)  class _CertificateSigningRequest(object):      def __init__(self, backend, x509_req): @@ -726,6 +907,15 @@ _EXTENSION_HANDLERS = {      ExtensionOID.NAME_CONSTRAINTS: _decode_name_constraints,  } +_REVOKED_EXTENSION_HANDLERS = { +    CRLExtensionOID.CRL_REASON: _decode_crl_reason, +    CRLExtensionOID.INVALIDITY_DATE: _decode_invalidity_date, +    CRLExtensionOID.CERTIFICATE_ISSUER: _decode_cert_issuer, +} + +_REVOKED_UNSUPPORTED_EXTENSIONS = set([ +    CRLExtensionOID.CERTIFICATE_ISSUER, +])  _CERTIFICATE_EXTENSION_PARSER = _X509ExtensionParser(      ext_count=lambda backend, x: backend._lib.X509_get_ext_count(x), @@ -738,3 +928,10 @@ _CSR_EXTENSION_PARSER = _X509ExtensionParser(      get_ext=lambda backend, x, i: backend._lib.sk_X509_EXTENSION_value(x, i),      handlers=_EXTENSION_HANDLERS  ) + +_REVOKED_CERTIFICATE_EXTENSION_PARSER = _X509ExtensionParser( +    ext_count=lambda backend, x: backend._lib.X509_REVOKED_get_ext_count(x), +    get_ext=lambda backend, x, i: backend._lib.X509_REVOKED_get_ext(x, i), +    handlers=_REVOKED_EXTENSION_HANDLERS, +    unsupported_exts=_REVOKED_UNSUPPORTED_EXTENSIONS +) diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py index 1aa2598b..70e1d3da 100644 --- a/src/cryptography/x509/__init__.py +++ b/src/cryptography/x509/__init__.py @@ -8,8 +8,8 @@ from cryptography.x509.base import (      Certificate, CertificateBuilder, CertificateRevocationList,      CertificateSigningRequest, CertificateSigningRequestBuilder,      InvalidVersion, RevokedCertificate, -    Version, load_der_x509_certificate, load_der_x509_csr, -    load_pem_x509_certificate, load_pem_x509_csr, +    Version, load_der_x509_certificate, load_der_x509_crl, load_der_x509_csr, +    load_pem_x509_certificate, load_pem_x509_crl, load_pem_x509_csr,  )  from cryptography.x509.extensions import (      AccessDescription, AuthorityInformationAccess, @@ -108,6 +108,8 @@ __all__ = [      "load_der_x509_certificate",      "load_pem_x509_csr",      "load_der_x509_csr", +    "load_pem_x509_crl", +    "load_der_x509_crl",      "InvalidVersion",      "DuplicateExtension",      "UnsupportedExtension", diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py index 27eafac6..01eadfcb 100644 --- a/src/cryptography/x509/base.py +++ b/src/cryptography/x509/base.py @@ -40,6 +40,14 @@ def load_der_x509_csr(data, backend):      return backend.load_der_x509_csr(data) +def load_pem_x509_crl(data, backend): +    return backend.load_pem_x509_crl(data) + + +def load_der_x509_crl(data, backend): +    return backend.load_der_x509_crl(data) + +  class InvalidVersion(Exception):      def __init__(self, msg, parsed_version):          super(InvalidVersion, self).__init__(msg) @@ -169,12 +177,6 @@ class CertificateRevocationList(object):          """      @abc.abstractproperty -    def revoked_certificates(self): -        """ -        Returns a list of RevokedCertificate objects for this CRL. -        """ - -    @abc.abstractproperty      def extensions(self):          """          Returns an Extensions object containing a list of CRL extensions. | 
