From dc57040a59ce4211a6ab6db843c497a87a825509 Mon Sep 17 00:00:00 2001 From: Erik Trauschke Date: Thu, 24 Sep 2015 20:24:28 -0700 Subject: OpenSSL backend code for CRLs --- docs/x509/reference.rst | 111 +++++++++ src/cryptography/hazmat/backends/multibackend.py | 18 ++ .../hazmat/backends/openssl/backend.py | 26 ++- src/cryptography/hazmat/backends/openssl/x509.py | 258 +++++++++++++++++++++ src/cryptography/x509/__init__.py | 6 +- src/cryptography/x509/base.py | 8 + tests/hazmat/backends/test_multibackend.py | 12 + tests/test_x509.py | 246 ++++++++++++++++++++ 8 files changed, 681 insertions(+), 4 deletions(-) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 62bdb3a9..9f9526e2 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -5,6 +5,21 @@ X.509 Reference .. testsetup:: + pem_crl_data = b""" + -----BEGIN X509 CRL----- + MIIBtDCBnQIBAjANBgkqhkiG9w0BAQsFADAnMQswCQYDVQQGEwJVUzEYMBYGA1UE + AwwPY3J5cHRvZ3JhcGh5LmlvGA8yMDE1MDEwMTAwMDAwMFoYDzIwMTYwMTAxMDAw + MDAwWjA+MDwCAQAYDzIwMTUwMTAxMDAwMDAwWjAmMBgGA1UdGAQRGA8yMDE1MDEw + MTAwMDAwMFowCgYDVR0VBAMKAQEwDQYJKoZIhvcNAQELBQADggEBABRA4ww50Lz5 + zk1j2+aluC4HPHqb7o06h4pTDcCGeXUKXIGeP5ntGGmIoxa26sNoLeOr8+5b43Gf + yWraHertllOwaOpNFEe+YZFaE9femtoDbf+GLMvRx/0wDfd3KxPoXnXKMXb2d1w4 + RCLgmkYx6JyvS+5ciuLQVIKC+l7jwIUeZFLJMUJ8msM4pFYoGameeZmtjMbd/TNg + cVBfmZxNMHuLladJxvSo2esARo0TYPhYsgrREKoHwhpzSxdynjn4bOVkILfguwsN + qtEEMZFEv5Kb0GqRp2+Iagv2S6dg9JGvxVdsoGjaB6EbYSZ3Psx4aODasIn11uwo + X4B9vUQNXqc= + -----END X509 CRL----- + """.strip() + pem_req_data = b""" -----BEGIN CERTIFICATE REQUEST----- MIIC0zCCAbsCAQAwWTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCElsbGlub2lzMRAw @@ -129,6 +144,52 @@ Loading Certificates >>> cert.serial 2 +Loading Certificate Revocation Lists +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. function:: load_pem_x509_crl(data, backend) + + .. versionadded:: 1.1 + + Deserialize a certificate revocation list (CRL) from PEM encoded data. PEM + requests are base64 decoded and have delimiters that look like + ``-----BEGIN X509 CRL-----``. This format is also known as + PKCS#10. + + :param bytes data: The PEM encoded request data. + + :param backend: A backend supporting the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :returns: An instance of + :class:`~cryptography.x509.CertificateRevocationList`. + +.. function:: load_der_x509_crl(data, backend) + + .. versionadded:: 1.1 + + Deserialize a certificate revocation list (CRL) from DER encoded data. DER + is a binary format. + + :param bytes data: The DER encoded request data. + + :param backend: A backend supporting the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :returns: An instance of + :class:`~cryptography.x509.CertificateRevocationList`. + +.. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> crl = x509.load_pem_x509_crl(pem_crl_data, default_backend()) + >>> isinstance(crl.signature_hash_algorithm, hashes.SHA256) + True + Loading Certificate Signing Requests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -349,6 +410,12 @@ X.509 CRL (Certificate Revocation List) Object :return bytes: The fingerprint using the supplied hash algorithm, as bytes. + .. doctest:: + + >>> from cryptography.hazmat.primitives import hashes + >>> crl.fingerprint(hashes.SHA256()) + 'e\xcf.\xc4:\x83?1\xdc\xf3\xfc\x95\xd7\xb3\x87\xb3\x8e\xf8\xb93!\x87\x07\x9d\x1b\xb4!\xb9\xe4W\xf4\x1f' + .. attribute:: signature_hash_algorithm :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` @@ -357,12 +424,23 @@ X.509 CRL (Certificate Revocation List) Object :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` which was used in signing this CRL. + .. doctest:: + + >>> from cryptography.hazmat.primitives import hashes + >>> isinstance(crl.signature_hash_algorithm, hashes.SHA256) + True + .. attribute:: issuer :type: :class:`Name` The :class:`Name` of the issuer. + .. doctest:: + + >>> crl.issuer + , value=u'US')>, , value=u'cryptography.io')>])> + .. attribute:: next_update :type: :class:`datetime.datetime` @@ -370,18 +448,34 @@ X.509 CRL (Certificate Revocation List) Object A naïve datetime representing when the next update to this CRL is expected. + .. doctest:: + + >>> crl.next_update + datetime.datetime(2016, 1, 1, 0, 0) + .. attribute:: last_update :type: :class:`datetime.datetime` A naïve datetime representing when the this CRL was last updated. + .. doctest:: + + >>> crl.last_update + datetime.datetime(2015, 1, 1, 0, 0) + .. attribute:: revoked_certificates :type: list of :class:`RevokedCertificate` The revoked certificates listed in this CRL. + .. doctest:: + + >>> for r in crl.revoked_certificates: + ... print(r.serial_number) + 0 + .. attribute:: extensions :type: :class:`Extensions` @@ -610,18 +704,35 @@ X.509 Revoked Certificate Object An integer representing the serial number of the revoked certificate. + .. doctest:: + + >>> crl.revoked_certificates[0].serial_number + 0 + .. attribute:: revocation_date :type: :class:`datetime.datetime` A naïve datetime representing the date this certificates was revoked. + .. doctest:: + + >>> crl.revoked_certificates[0].revocation_date + datetime.datetime(2015, 1, 1, 0, 0) + .. attribute:: extensions :type: :class:`Extensions` The extensions encoded in the revoked certificate. + .. doctest:: + + >>> for ext in crl.revoked_certificates[0].extensions: + ... print(ext) + , critical=False, value=2015-01-01 00:00:00)> + , critical=False, value=ReasonFlags.key_compromise)> + X.509 CSR (Certificate Signing Request) Builder Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py index 9db32aa5..cda33145 100644 --- a/src/cryptography/hazmat/backends/multibackend.py +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -325,6 +325,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 c74d90d4..8503b9b9 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -39,8 +39,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.binding import Binding from cryptography.hazmat.primitives import hashes, serialization @@ -1474,6 +1474,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 70cec163..79b516cc 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 @@ -637,6 +639,262 @@ def _decode_inhibit_any_policy(backend, asn1_int): return x509.InhibitAnyPolicy(skip_certs) +@utils.register_interface(x509.RevokedCertificate) +class _RevokedCertificate(object): + def __init__(self, backend, x509_revoked): + self._backend = backend + self._x509_revoked = x509_revoked + + self.__serial_number = None + self.__revocation_date = None + self.__extensions = None + + @property + def serial_number(self): + if self.__serial_number: + return self.__serial_number + + bn = self._backend._lib.ASN1_INTEGER_to_BN( + self._x509_revoked.serialNumber, self._backend._ffi.NULL + ) + assert bn != self._backend._ffi.NULL + bn = self._backend._ffi.gc(bn, self._backend._lib.BN_free) + self.__serial_number = self._backend._bn_to_int(bn) + return self.__serial_number + + @property + def revocation_date(self): + if self.__revocation_date: + return self.__revocation_date + + self.__revocation_date = self._backend._parse_asn1_time( + self._x509_revoked.revocationDate) + return self.__revocation_date + + @property + def extensions(self): + if self.__extensions: + return self.__extensions + + extensions = [] + seen_oids = set() + extcount = self._backend._lib.X509_REVOKED_get_ext_count( + self._x509_revoked) + for i in range(0, extcount): + ext = self._backend._lib.X509_REVOKED_get_ext( + self._x509_revoked, i) + assert ext != self._backend._ffi.NULL + crit = self._backend._lib.X509_EXTENSION_get_critical(ext) + critical = crit == 1 + oid = x509.ObjectIdentifier(_obj2txt(self._backend, ext.object)) + if oid in seen_oids: + raise x509.DuplicateExtension( + "Duplicate {0} extension found".format(oid), oid + ) + + if oid == x509.OID_CRL_REASON: + value = self._build_crl_reason(ext) + elif oid == x509.OID_INVALIDITY_DATE: + value = self._build_invalidity_date(ext) + elif oid == x509.OID_CERTIFICATE_ISSUER and \ + self._backend._lib.OPENSSL_VERSION_NUMBER >= 0x10000000: + value = self._build_cert_issuer(ext) + elif critical: + raise x509.UnsupportedExtension( + "{0} is not currently supported".format(oid), oid + ) + else: + # Unsupported non-critical extension, silently skipping for now + seen_oids.add(oid) + continue + + seen_oids.add(oid) + extensions.append(x509.Extension(oid, critical, value)) + + self.__extensions = x509.Extensions(extensions) + return self.__extensions + + def get_reason(self): + """ + Returns the CRLReason extension if it exists. + """ + try: + return self.extensions.get_extension_for_oid( + x509.OID_CRL_REASON).value + except x509.ExtensionNotFound: + return None + + def get_invalidity_date(self): + """ + Returns the InvalidityDate extension if it exists. + """ + try: + return self.extensions.get_extension_for_oid( + x509.OID_INVALIDITY_DATE).value + except x509.ExtensionNotFound: + return None + + def get_certificate_issuer(self): + """ + Returns the CertificateIssuer extension if it exists. + """ + try: + return self.extensions.get_extension_for_oid( + x509.OID_CERTIFICATE_ISSUER).value + except x509.ExtensionNotFound: + return None + + def _build_crl_reason(self, ext): + enum = self._backend._lib.X509V3_EXT_d2i(ext) + assert enum != self._backend._ffi.NULL + enum = self._backend._ffi.cast("ASN1_ENUMERATED *", enum) + enum = self._backend._ffi.gc( + enum, self._backend._lib.ASN1_ENUMERATED_free + ) + code = self._backend._lib.ASN1_ENUMERATED_get(enum) + if code == 0: + return x509.ReasonFlags.unspecified + elif code == 1: + return x509.ReasonFlags.key_compromise + elif code == 2: + return x509.ReasonFlags.ca_compromise + elif code == 3: + return x509.ReasonFlags.affiliation_changed + elif code == 4: + return x509.ReasonFlags.superseded + elif code == 5: + return x509.ReasonFlags.cessation_of_operation + elif code == 6: + return x509.ReasonFlags.certificate_hold + elif code == 8: + return x509.ReasonFlags.remove_from_crl + elif code == 9: + return x509.ReasonFlags.privilege_withdrawn + elif code == 10: + return x509.ReasonFlags.aa_compromise + else: + raise ValueError("Unsupported reason code: {0}".format(code)) + + def _build_invalidity_date(self, ext): + generalized_time = self._backend._ffi.cast( + "ASN1_GENERALIZEDTIME *", + self._backend._lib.X509V3_EXT_d2i(ext) + ) + assert generalized_time != self._backend._ffi.NULL + generalized_time = self._backend._ffi.gc( + generalized_time, self._backend._lib.ASN1_GENERALIZEDTIME_free + ) + time = self._backend._ffi.string( + self._backend._lib.ASN1_STRING_data( + self._backend._ffi.cast("ASN1_STRING *", generalized_time) + ) + ).decode("ascii") + return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ") + + def _build_cert_issuer(self, ext): + gns = self._backend._ffi.cast( + "GENERAL_NAMES *", self._backend._lib.X509V3_EXT_d2i(ext) + ) + assert gns != self._backend._ffi.NULL + gns = self._backend._ffi.gc(gns, self._backend._lib.GENERAL_NAMES_free) + return x509.GeneralNames(_decode_general_names(self._backend, gns)) + + +@utils.register_interface(x509.CertificateRevocationList) +class _CertificateRevocationList(object): + def __init__(self, backend, x509_crl): + self._backend = backend + self._x509_crl = x509_crl + + self.__revoked = None + self.__issuer = None + self.__next_update = None + self.__last_update = None + + 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 + ) + 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): + if self.__issuer: + return self.__issuer + + issuer = self._backend._lib.X509_CRL_get_issuer(self._x509_crl) + assert issuer != self._backend._ffi.NULL + self.__issuer = _decode_x509_name(self._backend, issuer) + return self.__issuer + + @property + def next_update(self): + if self.__next_update: + return self.__next_update + + nu = self._backend._lib.X509_CRL_get_nextUpdate(self._x509_crl) + assert nu != self._backend._ffi.NULL + self.__next_update = self._backend._parse_asn1_time(nu) + return self.__next_update + + @property + def last_update(self): + if self.__last_update: + return self.__last_update + + lu = self._backend._lib.X509_CRL_get_lastUpdate(self._x509_crl) + assert lu != self._backend._ffi.NULL + self.__last_update = self._backend._parse_asn1_time(lu) + return self.__last_update + + @property + def revoked_certificates(self): + if self.__revoked: + return self.__revoked + + revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl) + 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) + assert r != self._backend._ffi.NULL + revoked_list.append(_RevokedCertificate(self._backend, r)) + + self.__revoked = revoked_list + return self.__revoked + + @property + def extensions(self): + raise NotImplementedError() + + @utils.register_interface(x509.CertificateSigningRequest) class _CertificateSigningRequest(object): def __init__(self, backend, x509_req): 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..9dc49a60 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) diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py index 4d17cdb0..618d21b6 100644 --- a/tests/hazmat/backends/test_multibackend.py +++ b/tests/hazmat/backends/test_multibackend.py @@ -197,6 +197,12 @@ class DummyX509Backend(object): def load_der_x509_certificate(self, data): pass + def load_pem_x509_crl(self, data): + pass + + def load_der_x509_crl(self, data): + pass + def load_pem_x509_csr(self, data): pass @@ -491,6 +497,8 @@ class TestMultiBackend(object): backend.load_pem_x509_certificate(b"certdata") backend.load_der_x509_certificate(b"certdata") + backend.load_pem_x509_crl(b"crldata") + backend.load_der_x509_crl(b"crldata") backend.load_pem_x509_csr(b"reqdata") backend.load_der_x509_csr(b"reqdata") backend.create_x509_csr(object(), b"privatekey", hashes.SHA1()) @@ -501,6 +509,10 @@ class TestMultiBackend(object): backend.load_pem_x509_certificate(b"certdata") with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): backend.load_der_x509_certificate(b"certdata") + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.load_pem_x509_crl(b"crldata") + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.load_der_x509_crl(b"crldata") with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): backend.load_pem_x509_csr(b"reqdata") with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): diff --git a/tests/test_x509.py b/tests/test_x509.py index 220e71a5..f5fead53 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -52,6 +52,252 @@ def _load_cert(filename, loader, backend): return cert +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestCertificateRevocationList(object): + def test_load_pem_crl(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + assert isinstance(crl, x509.CertificateRevocationList) + fingerprint = binascii.hexlify(crl.fingerprint(hashes.SHA1())) + assert fingerprint == b"3234b0cb4c0cedf6423724b736729dcfc9e441ef" + assert isinstance(crl.signature_hash_algorithm, hashes.SHA256) + + def test_load_der_crl(self, backend): + crl = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + assert isinstance(crl, x509.CertificateRevocationList) + fingerprint = binascii.hexlify(crl.fingerprint(hashes.SHA1())) + assert fingerprint == b"dd3db63c50f4c4a13e090f14053227cb1011a5ad" + assert isinstance(crl.signature_hash_algorithm, hashes.SHA256) + + def test_invalid_pem(self, backend): + with pytest.raises(ValueError): + x509.load_pem_x509_crl(b"notacrl", backend) + + def test_invalid_der(self, backend): + with pytest.raises(ValueError): + x509.load_der_x509_crl(b"notacrl", backend) + + def test_unknown_signature_algorithm(self, backend): + crl = _load_cert( + os.path.join( + "x509", "custom", "crl_md2_unknown_crit_entry_ext.pem" + ), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(UnsupportedAlgorithm): + crl.signature_hash_algorithm() + + def test_issuer(self, backend): + crl = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + assert isinstance(crl.issuer, x509.Name) + assert list(crl.issuer) == [ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute( + x509.OID_ORGANIZATION_NAME, u'Test Certificates 2011' + ), + x509.NameAttribute(x509.OID_COMMON_NAME, u'Good CA') + ] + assert crl.issuer.get_attributes_for_oid(x509.OID_COMMON_NAME) == [ + x509.NameAttribute(x509.OID_COMMON_NAME, u'Good CA') + ] + + def test_equality(self, backend): + crl1 = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + crl2 = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + crl3 = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + assert crl1 == crl2 + assert crl1 != crl3 + assert crl1 != object() + + def test_update_dates(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + assert isinstance(crl.next_update, datetime.datetime) + assert isinstance(crl.last_update, datetime.datetime) + + assert crl.next_update.isoformat() == "2016-01-01T00:00:00" + assert crl.last_update.isoformat() == "2015-01-01T00:00:00" + + def test_revoked_certs(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + assert isinstance(crl.revoked_certificates, list) + for r in crl.revoked_certificates: + assert isinstance(r, x509.RevokedCertificate) + + def test_extensions(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + # CRL extensions are currently not supported in the OpenSSL backend. + with pytest.raises(NotImplementedError): + crl.extensions + + +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestRevokedCertificate(object): + + def test_revoked_basics(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + for i, rev in enumerate(crl.revoked_certificates): + assert isinstance(rev, x509.RevokedCertificate) + assert isinstance(rev.serial_number, int) + assert isinstance(rev.revocation_date, datetime.datetime) + assert isinstance(rev.extensions, x509.Extensions) + + assert rev.serial_number == i + assert rev.revocation_date.isoformat() == "2015-01-01T00:00:00" + + def test_revoked_extensions(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + # First revoked cert doesn't have extensions, test if it is handled + # correctly. + rev0 = crl.revoked_certificates[0] + # It should return an empty Extensions object. + assert isinstance(rev0.extensions, x509.Extensions) + assert len(rev0.extensions) == 0 + with pytest.raises(x509.ExtensionNotFound): + rev0.extensions.get_extension_for_oid(x509.OID_CRL_REASON) + + assert rev0.get_invalidity_date() is None + assert rev0.get_certificate_issuer() is None + assert rev0.get_reason() is None + + # Test manual retrieval of extension values. + rev1 = crl.revoked_certificates[1] + assert isinstance(rev1.extensions, x509.Extensions) + + reason = rev1.extensions.get_extension_for_oid( + x509.OID_CRL_REASON).value + assert reason == x509.ReasonFlags.unspecified + + date = rev1.extensions.get_extension_for_oid( + x509.OID_INVALIDITY_DATE).value + assert isinstance(date, datetime.datetime) + assert date.isoformat() == "2015-01-01T00:00:00" + + # Test convenience function. + assert rev1.get_invalidity_date().isoformat() == "2015-01-01T00:00:00" + + # Check if all reason flags can be found in the CRL. + flags = set(x509.ReasonFlags) + # The first revoked cert doesn't have a reason. + for r in crl.revoked_certificates[1:]: + flags.discard(r.get_reason()) + assert len(flags) == 0 + + def test_duplicate_entry_ext(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_dup_entry_ext.pem"), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(x509.DuplicateExtension): + crl.revoked_certificates[0].extensions + + def test_unsupported_crit_entry_ext(self, backend): + crl = _load_cert( + os.path.join( + "x509", "custom", "crl_md2_unknown_crit_entry_ext.pem" + ), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(x509.UnsupportedExtension): + crl.revoked_certificates[0].extensions + + def test_unsupported_reason(self, backend): + crl = _load_cert( + os.path.join( + "x509", "custom", "crl_unsupported_reason.pem" + ), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(ValueError): + crl.revoked_certificates[0].extensions + + def test_cert_issuer_ext(self, backend): + if backend._lib.OPENSSL_VERSION_NUMBER < 0x10000000: + pytest.skip("Requires a newer OpenSSL. Must be at least 1.0.0") + + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + exp_issuer = x509.GeneralNames([ + x509.DirectoryName(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), + x509.NameAttribute(x509.OID_COMMON_NAME, u"cryptography.io"), + ])) + ]) + + rev = crl.revoked_certificates[1] + issuer = rev.extensions.get_extension_for_oid( + x509.OID_CERTIFICATE_ISSUER).value + assert issuer == exp_issuer + + # Test convenience function. + assert rev.get_certificate_issuer() == exp_issuer + + @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) class TestRSACertificate(object): -- cgit v1.2.3 From 396a282fd5febabe382dcdacf4e8a6c2395f0087 Mon Sep 17 00:00:00 2001 From: Erik Trauschke Date: Mon, 28 Sep 2015 08:56:18 -0700 Subject: use openssl assert change _build* to _decode* make CRLs into iterators various fixes --- src/cryptography/hazmat/backends/openssl/x509.py | 49 +++++++++++++----------- tests/test_x509.py | 7 +++- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index 8a13aa5d..a0ad87ec 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -654,12 +654,9 @@ class _RevokedCertificate(object): if self.__serial_number: return self.__serial_number - bn = self._backend._lib.ASN1_INTEGER_to_BN( - self._x509_revoked.serialNumber, self._backend._ffi.NULL - ) - assert bn != self._backend._ffi.NULL - bn = self._backend._ffi.gc(bn, self._backend._lib.BN_free) - self.__serial_number = self._backend._bn_to_int(bn) + asn1_int = self._x509_revoked.serialNumber + self._backend.openssl_assert(asn1_int != self._backend._ffi.NULL) + self.__serial_number = self._backend._asn1_integer_to_int(asn1_int) return self.__serial_number @property @@ -683,7 +680,7 @@ class _RevokedCertificate(object): for i in range(0, extcount): ext = self._backend._lib.X509_REVOKED_get_ext( self._x509_revoked, i) - assert ext != self._backend._ffi.NULL + self._backend.openssl_assert(ext != self._backend._ffi.NULL) crit = self._backend._lib.X509_EXTENSION_get_critical(ext) critical = crit == 1 oid = x509.ObjectIdentifier(_obj2txt(self._backend, ext.object)) @@ -693,12 +690,12 @@ class _RevokedCertificate(object): ) if oid == x509.OID_CRL_REASON: - value = self._build_crl_reason(ext) + value = self._decode_crl_reason(ext) elif oid == x509.OID_INVALIDITY_DATE: - value = self._build_invalidity_date(ext) + value = self._decode_invalidity_date(ext) elif oid == x509.OID_CERTIFICATE_ISSUER and \ self._backend._lib.OPENSSL_VERSION_NUMBER >= 0x10000000: - value = self._build_cert_issuer(ext) + value = self._decode_cert_issuer(ext) elif critical: raise x509.UnsupportedExtension( "{0} is not currently supported".format(oid), oid @@ -744,9 +741,9 @@ class _RevokedCertificate(object): except x509.ExtensionNotFound: return None - def _build_crl_reason(self, ext): + def _decode_crl_reason(self, ext): enum = self._backend._lib.X509V3_EXT_d2i(ext) - assert enum != self._backend._ffi.NULL + self._backend.openssl_assert(enum != self._backend._ffi.NULL) enum = self._backend._ffi.cast("ASN1_ENUMERATED *", enum) enum = self._backend._ffi.gc( enum, self._backend._lib.ASN1_ENUMERATED_free @@ -775,12 +772,14 @@ class _RevokedCertificate(object): else: raise ValueError("Unsupported reason code: {0}".format(code)) - def _build_invalidity_date(self, ext): + def _decode_invalidity_date(self, ext): generalized_time = self._backend._ffi.cast( "ASN1_GENERALIZEDTIME *", self._backend._lib.X509V3_EXT_d2i(ext) ) - assert generalized_time != self._backend._ffi.NULL + self._backend.openssl_assert( + generalized_time != self._backend._ffi.NULL + ) generalized_time = self._backend._ffi.gc( generalized_time, self._backend._lib.ASN1_GENERALIZEDTIME_free ) @@ -791,11 +790,11 @@ class _RevokedCertificate(object): ).decode("ascii") return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ") - def _build_cert_issuer(self, ext): + def _decode_cert_issuer(self, ext): gns = self._backend._ffi.cast( "GENERAL_NAMES *", self._backend._lib.X509V3_EXT_d2i(ext) ) - assert gns != self._backend._ffi.NULL + self._backend.openssl_assert(gns != self._backend._ffi.NULL) gns = self._backend._ffi.gc(gns, self._backend._lib.GENERAL_NAMES_free) return x509.GeneralNames(_decode_general_names(self._backend, gns)) @@ -827,7 +826,7 @@ class _CertificateRevocationList(object): res = self._backend._lib.i2d_X509_CRL_bio( bio, self._x509_crl ) - assert res == 1 + self._backend.openssl_assert(res == 1) der = self._backend._read_mem_bio(bio) h.update(der) return h.finalize() @@ -848,7 +847,7 @@ class _CertificateRevocationList(object): return self.__issuer issuer = self._backend._lib.X509_CRL_get_issuer(self._x509_crl) - assert issuer != self._backend._ffi.NULL + self._backend.openssl_assert(issuer != self._backend._ffi.NULL) self.__issuer = _decode_x509_name(self._backend, issuer) return self.__issuer @@ -858,7 +857,7 @@ class _CertificateRevocationList(object): return self.__next_update nu = self._backend._lib.X509_CRL_get_nextUpdate(self._x509_crl) - assert nu != self._backend._ffi.NULL + self._backend.openssl_assert(nu != self._backend._ffi.NULL) self.__next_update = self._backend._parse_asn1_time(nu) return self.__next_update @@ -868,7 +867,7 @@ class _CertificateRevocationList(object): return self.__last_update lu = self._backend._lib.X509_CRL_get_lastUpdate(self._x509_crl) - assert lu != self._backend._ffi.NULL + self._backend.openssl_assert(lu != self._backend._ffi.NULL) self.__last_update = self._backend._parse_asn1_time(lu) return self.__last_update @@ -878,18 +877,24 @@ class _CertificateRevocationList(object): return self.__revoked revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl) - assert revoked != self._backend._ffi.NULL + 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) - assert r != self._backend._ffi.NULL + self._backend.openssl_assert(r != self._backend._ffi.NULL) revoked_list.append(_RevokedCertificate(self._backend, r)) self.__revoked = revoked_list return self.__revoked + def __iter__(self): + return iter(self.revoked_certificates) + + def __len__(self): + return len(self.revoked_certificates) + @property def extensions(self): raise NotImplementedError() diff --git a/tests/test_x509.py b/tests/test_x509.py index f5fead53..c380b860 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -232,12 +232,15 @@ class TestRevokedCertificate(object): assert rev1.get_invalidity_date().isoformat() == "2015-01-01T00:00:00" # Check if all reason flags can be found in the CRL. + # Also test if CRL as iterator works. flags = set(x509.ReasonFlags) - # The first revoked cert doesn't have a reason. - for r in crl.revoked_certificates[1:]: + for r in crl: flags.discard(r.get_reason()) assert len(flags) == 0 + # Check that len() works for CRLs. + assert len(crl) == 12 + def test_duplicate_entry_ext(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_dup_entry_ext.pem"), -- cgit v1.2.3 From 3cdabaf2544b98c6c915ca6704f9e03fdd58e1e6 Mon Sep 17 00:00:00 2001 From: Erik Trauschke Date: Tue, 13 Oct 2015 09:42:53 -0700 Subject: fix indentations change docs to indicate CRL objects are iterable fix docs for revoked certs make _decode_crl_reason more readable add __getitem__ method to CRL object remove double underscores --- docs/x509/reference.rst | 16 ++- src/cryptography/hazmat/backends/openssl/x509.py | 133 +++++++++++------------ tests/test_x509.py | 3 + 3 files changed, 83 insertions(+), 69 deletions(-) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 9f9526e2..eda6378f 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -401,6 +401,19 @@ X.509 CRL (Certificate Revocation List) Object .. versionadded:: 1.0 + A CertificateRevocationList is an object representing a list of revoked + certificates. The object is iterable and will yield the RevokedCertificate + objects stored in this CRL. + + .. doctest:: + >>> len(crl) + 1 + >>> type(crl[0]) + + >>> for r in crl: + ... print(r.serial_number) + 0 + .. method:: fingerprint(algorithm) :param algorithm: The @@ -706,7 +719,8 @@ X.509 Revoked Certificate Object .. doctest:: - >>> crl.revoked_certificates[0].serial_number + >>> revoked_certificate = crl.revoked_certificates[0] + >>> revoked_certificate.serial_number 0 .. attribute:: revocation_date diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index a0ad87ec..32678939 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -645,39 +645,39 @@ class _RevokedCertificate(object): self._backend = backend self._x509_revoked = x509_revoked - self.__serial_number = None - self.__revocation_date = None - self.__extensions = None + self._serial_number = None + self._revocation_date = None + self._extensions = None @property def serial_number(self): - if self.__serial_number: - return self.__serial_number + if self._serial_number: + return self._serial_number asn1_int = self._x509_revoked.serialNumber self._backend.openssl_assert(asn1_int != self._backend._ffi.NULL) - self.__serial_number = self._backend._asn1_integer_to_int(asn1_int) - return self.__serial_number + self._serial_number = self._backend._asn1_integer_to_int(asn1_int) + return self._serial_number @property def revocation_date(self): - if self.__revocation_date: - return self.__revocation_date + if self._revocation_date: + return self._revocation_date - self.__revocation_date = self._backend._parse_asn1_time( + self._revocation_date = self._backend._parse_asn1_time( self._x509_revoked.revocationDate) - return self.__revocation_date + return self._revocation_date @property def extensions(self): - if self.__extensions: - return self.__extensions + if self._extensions: + return self._extensions extensions = [] seen_oids = set() extcount = self._backend._lib.X509_REVOKED_get_ext_count( self._x509_revoked) - for i in range(0, extcount): + for i in range(extcount): ext = self._backend._lib.X509_REVOKED_get_ext( self._x509_revoked, i) self._backend.openssl_assert(ext != self._backend._ffi.NULL) @@ -693,8 +693,8 @@ class _RevokedCertificate(object): value = self._decode_crl_reason(ext) elif oid == x509.OID_INVALIDITY_DATE: value = self._decode_invalidity_date(ext) - elif oid == x509.OID_CERTIFICATE_ISSUER and \ - self._backend._lib.OPENSSL_VERSION_NUMBER >= 0x10000000: + elif (oid == x509.OID_CERTIFICATE_ISSUER and + self._backend._lib.OPENSSL_VERSION_NUMBER >= 0x10000000): value = self._decode_cert_issuer(ext) elif critical: raise x509.UnsupportedExtension( @@ -708,38 +708,38 @@ class _RevokedCertificate(object): seen_oids.add(oid) extensions.append(x509.Extension(oid, critical, value)) - self.__extensions = x509.Extensions(extensions) - return self.__extensions + self._extensions = x509.Extensions(extensions) + return self._extensions def get_reason(self): """ Returns the CRLReason extension if it exists. """ try: - return self.extensions.get_extension_for_oid( - x509.OID_CRL_REASON).value + return self.extensions.get_extension_for_oid( + x509.OID_CRL_REASON).value except x509.ExtensionNotFound: - return None + return None def get_invalidity_date(self): """ Returns the InvalidityDate extension if it exists. """ try: - return self.extensions.get_extension_for_oid( - x509.OID_INVALIDITY_DATE).value + return self.extensions.get_extension_for_oid( + x509.OID_INVALIDITY_DATE).value except x509.ExtensionNotFound: - return None + return None def get_certificate_issuer(self): """ Returns the CertificateIssuer extension if it exists. """ try: - return self.extensions.get_extension_for_oid( - x509.OID_CERTIFICATE_ISSUER).value + return self.extensions.get_extension_for_oid( + x509.OID_CERTIFICATE_ISSUER).value except x509.ExtensionNotFound: - return None + return None def _decode_crl_reason(self, ext): enum = self._backend._lib.X509V3_EXT_d2i(ext) @@ -749,27 +749,21 @@ class _RevokedCertificate(object): enum, self._backend._lib.ASN1_ENUMERATED_free ) code = self._backend._lib.ASN1_ENUMERATED_get(enum) - if code == 0: - return x509.ReasonFlags.unspecified - elif code == 1: - return x509.ReasonFlags.key_compromise - elif code == 2: - return x509.ReasonFlags.ca_compromise - elif code == 3: - return x509.ReasonFlags.affiliation_changed - elif code == 4: - return x509.ReasonFlags.superseded - elif code == 5: - return x509.ReasonFlags.cessation_of_operation - elif code == 6: - return x509.ReasonFlags.certificate_hold - elif code == 8: - return x509.ReasonFlags.remove_from_crl - elif code == 9: - return x509.ReasonFlags.privilege_withdrawn - elif code == 10: - return x509.ReasonFlags.aa_compromise - else: + + 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(self, ext): @@ -805,10 +799,10 @@ class _CertificateRevocationList(object): self._backend = backend self._x509_crl = x509_crl - self.__revoked = None - self.__issuer = None - self.__next_update = None - self.__last_update = None + self._revoked = None + self._issuer = None + self._next_update = None + self._last_update = None def __eq__(self, other): if not isinstance(other, x509.CertificateRevocationList): @@ -843,38 +837,38 @@ class _CertificateRevocationList(object): @property def issuer(self): - if self.__issuer: - return self.__issuer + if self._issuer: + return self._issuer issuer = self._backend._lib.X509_CRL_get_issuer(self._x509_crl) self._backend.openssl_assert(issuer != self._backend._ffi.NULL) - self.__issuer = _decode_x509_name(self._backend, issuer) - return self.__issuer + self._issuer = _decode_x509_name(self._backend, issuer) + return self._issuer @property def next_update(self): - if self.__next_update: - return self.__next_update + if self._next_update: + return self._next_update nu = self._backend._lib.X509_CRL_get_nextUpdate(self._x509_crl) self._backend.openssl_assert(nu != self._backend._ffi.NULL) - self.__next_update = self._backend._parse_asn1_time(nu) - return self.__next_update + self._next_update = self._backend._parse_asn1_time(nu) + return self._next_update @property def last_update(self): - if self.__last_update: - return self.__last_update + if self._last_update: + return self._last_update lu = self._backend._lib.X509_CRL_get_lastUpdate(self._x509_crl) self._backend.openssl_assert(lu != self._backend._ffi.NULL) - self.__last_update = self._backend._parse_asn1_time(lu) - return self.__last_update + self._last_update = self._backend._parse_asn1_time(lu) + return self._last_update @property def revoked_certificates(self): - if self.__revoked: - return self.__revoked + if self._revoked: + return self._revoked revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl) self._backend.openssl_assert(revoked != self._backend._ffi.NULL) @@ -886,12 +880,15 @@ class _CertificateRevocationList(object): self._backend.openssl_assert(r != self._backend._ffi.NULL) revoked_list.append(_RevokedCertificate(self._backend, r)) - self.__revoked = revoked_list - return self.__revoked + self._revoked = revoked_list + return self._revoked 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) diff --git a/tests/test_x509.py b/tests/test_x509.py index c380b860..61e7a7d0 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -241,6 +241,9 @@ class TestRevokedCertificate(object): # Check that len() works for CRLs. assert len(crl) == 12 + # Check that direct access to revoked cert in CRL works + assert isinstance(crl[0], x509.RevokedCertificate) + def test_duplicate_entry_ext(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_dup_entry_ext.pem"), -- cgit v1.2.3 From b14f7d1c580bf0b9d4b6a8a14503c70aa8576134 Mon Sep 17 00:00:00 2001 From: Erik Trauschke Date: Tue, 13 Oct 2015 09:55:39 -0700 Subject: doctest fix --- docs/x509/reference.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index eda6378f..5f4c5314 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -406,6 +406,7 @@ X.509 CRL (Certificate Revocation List) Object objects stored in this CRL. .. doctest:: + >>> len(crl) 1 >>> type(crl[0]) -- cgit v1.2.3 From 77f5a2560a2c637364467a5f74b60a0e70e177f9 Mon Sep 17 00:00:00 2001 From: Erik Trauschke Date: Wed, 14 Oct 2015 08:06:38 -0700 Subject: use X509ExtensionParser for Revoked extensions remove revoked_certificates property from RevokedCertificate class CRLExtensions should actually be RevokedExtensions doctest cleanup for RevokedCertificate --- docs/x509/reference.rst | 21 +-- src/cryptography/hazmat/backends/openssl/x509.py | 179 ++++++++++------------- src/cryptography/x509/__init__.py | 8 +- src/cryptography/x509/base.py | 6 - src/cryptography/x509/oid.py | 8 +- tests/test_x509.py | 29 ++-- 6 files changed, 106 insertions(+), 145 deletions(-) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 5f4c5314..67591d38 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -478,18 +478,6 @@ X.509 CRL (Certificate Revocation List) Object >>> crl.last_update datetime.datetime(2015, 1, 1, 0, 0) - .. attribute:: revoked_certificates - - :type: list of :class:`RevokedCertificate` - - The revoked certificates listed in this CRL. - - .. doctest:: - - >>> for r in crl.revoked_certificates: - ... print(r.serial_number) - 0 - .. attribute:: extensions :type: :class:`Extensions` @@ -712,6 +700,10 @@ X.509 Revoked Certificate Object .. versionadded:: 1.0 + .. doctest:: + + >>> revoked_certificate = crl[0] + .. attribute:: serial_number :type: :class:`int` @@ -720,7 +712,6 @@ X.509 Revoked Certificate Object .. doctest:: - >>> revoked_certificate = crl.revoked_certificates[0] >>> revoked_certificate.serial_number 0 @@ -732,7 +723,7 @@ X.509 Revoked Certificate Object .. doctest:: - >>> crl.revoked_certificates[0].revocation_date + >>> revoked_certificate.revocation_date datetime.datetime(2015, 1, 1, 0, 0) .. attribute:: extensions @@ -743,7 +734,7 @@ X.509 Revoked Certificate Object .. doctest:: - >>> for ext in crl.revoked_certificates[0].extensions: + >>> for ext in revoked_certificate.extensions: ... print(ext) , critical=False, value=2015-01-01 00:00:00)> , critical=False, value=ReasonFlags.key_compromise)> diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index 32678939..7f7be545 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -18,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 ( + CertificatePoliciesOID, ExtensionOID, RevokedExtensionOID +) def _obj2txt(backend, obj): @@ -173,10 +175,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, supported_versions=None): self.ext_count = ext_count self.get_ext = get_ext self.handlers = handlers + self.supported_versions = supported_versions def parse(self, backend, x509_obj): extensions = [] @@ -187,6 +190,13 @@ class _X509ExtensionParser(object): crit = backend._lib.X509_EXTENSION_get_critical(ext) critical = crit == 1 oid = x509.ObjectIdentifier(_obj2txt(backend, ext.object)) + + # Filter out extensions we know are not supported by the backend + if (self.supported_versions and oid in self.supported_versions and + self.supported_versions[oid] > + backend._lib.OPENSSL_VERSION_NUMBER): + self.handlers.pop(oid, None) + if oid in seen_oids: raise x509.DuplicateExtension( "Duplicate {0} extension found".format(oid), oid @@ -196,7 +206,8 @@ 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) @@ -639,6 +650,49 @@ 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, issuer): + gns = backend._ffi.cast("GENERAL_NAMES *", 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): @@ -670,46 +724,9 @@ class _RevokedCertificate(object): @property def extensions(self): - if self._extensions: - return self._extensions - - extensions = [] - seen_oids = set() - extcount = self._backend._lib.X509_REVOKED_get_ext_count( - self._x509_revoked) - for i in range(extcount): - ext = self._backend._lib.X509_REVOKED_get_ext( - self._x509_revoked, i) - self._backend.openssl_assert(ext != self._backend._ffi.NULL) - crit = self._backend._lib.X509_EXTENSION_get_critical(ext) - critical = crit == 1 - oid = x509.ObjectIdentifier(_obj2txt(self._backend, ext.object)) - if oid in seen_oids: - raise x509.DuplicateExtension( - "Duplicate {0} extension found".format(oid), oid - ) - - if oid == x509.OID_CRL_REASON: - value = self._decode_crl_reason(ext) - elif oid == x509.OID_INVALIDITY_DATE: - value = self._decode_invalidity_date(ext) - elif (oid == x509.OID_CERTIFICATE_ISSUER and - self._backend._lib.OPENSSL_VERSION_NUMBER >= 0x10000000): - value = self._decode_cert_issuer(ext) - elif critical: - raise x509.UnsupportedExtension( - "{0} is not currently supported".format(oid), oid - ) - else: - # Unsupported non-critical extension, silently skipping for now - seen_oids.add(oid) - continue - - seen_oids.add(oid) - extensions.append(x509.Extension(oid, critical, value)) - - self._extensions = x509.Extensions(extensions) - return self._extensions + return _REVOKED_CERTIFICATE_EXTENSION_PARSER.parse( + self._backend, self._x509_revoked + ) def get_reason(self): """ @@ -741,57 +758,6 @@ class _RevokedCertificate(object): except x509.ExtensionNotFound: return None - def _decode_crl_reason(self, ext): - enum = self._backend._lib.X509V3_EXT_d2i(ext) - self._backend.openssl_assert(enum != self._backend._ffi.NULL) - enum = self._backend._ffi.cast("ASN1_ENUMERATED *", enum) - enum = self._backend._ffi.gc( - enum, self._backend._lib.ASN1_ENUMERATED_free - ) - code = self._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(self, ext): - generalized_time = self._backend._ffi.cast( - "ASN1_GENERALIZEDTIME *", - self._backend._lib.X509V3_EXT_d2i(ext) - ) - self._backend.openssl_assert( - generalized_time != self._backend._ffi.NULL - ) - generalized_time = self._backend._ffi.gc( - generalized_time, self._backend._lib.ASN1_GENERALIZEDTIME_free - ) - time = self._backend._ffi.string( - self._backend._lib.ASN1_STRING_data( - self._backend._ffi.cast("ASN1_STRING *", generalized_time) - ) - ).decode("ascii") - return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ") - - def _decode_cert_issuer(self, ext): - gns = self._backend._ffi.cast( - "GENERAL_NAMES *", self._backend._lib.X509V3_EXT_d2i(ext) - ) - self._backend.openssl_assert(gns != self._backend._ffi.NULL) - gns = self._backend._ffi.gc(gns, self._backend._lib.GENERAL_NAMES_free) - return x509.GeneralNames(_decode_general_names(self._backend, gns)) - @utils.register_interface(x509.CertificateRevocationList) class _CertificateRevocationList(object): @@ -865,8 +831,7 @@ class _CertificateRevocationList(object): self._last_update = self._backend._parse_asn1_time(lu) return self._last_update - @property - def revoked_certificates(self): + def _revoked_certificates(self): if self._revoked: return self._revoked @@ -884,13 +849,13 @@ class _CertificateRevocationList(object): return self._revoked def __iter__(self): - return iter(self.revoked_certificates) + return iter(self._revoked_certificates()) def __getitem__(self, idx): - return self.revoked_certificates[idx] + return self._revoked_certificates()[idx] def __len__(self): - return len(self.revoked_certificates) + return len(self._revoked_certificates()) @property def extensions(self): @@ -977,6 +942,15 @@ _EXTENSION_HANDLERS = { ExtensionOID.NAME_CONSTRAINTS: _decode_name_constraints, } +_REVOKED_EXTENSION_HANDLERS = { + RevokedExtensionOID.CRL_REASON: _decode_crl_reason, + RevokedExtensionOID.INVALIDITY_DATE: _decode_invalidity_date, + RevokedExtensionOID.CERTIFICATE_ISSUER: _decode_cert_issuer, +} + +_REVOKED_SUPPORTED_VERSIONS = { + RevokedExtensionOID.CERTIFICATE_ISSUER: 0x10000000, +} _CERTIFICATE_EXTENSION_PARSER = _X509ExtensionParser( ext_count=lambda backend, x: backend._lib.X509_get_ext_count(x), @@ -989,3 +963,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, + supported_versions=_REVOKED_SUPPORTED_VERSIONS +) diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py index 70e1d3da..697df6f2 100644 --- a/src/cryptography/x509/__init__.py +++ b/src/cryptography/x509/__init__.py @@ -28,7 +28,7 @@ from cryptography.x509.general_name import ( ) from cryptography.x509.name import Name, NameAttribute from cryptography.x509.oid import ( - AuthorityInformationAccessOID, CRLExtensionOID, CertificatePoliciesOID, + AuthorityInformationAccessOID, RevokedExtensionOID, CertificatePoliciesOID, ExtendedKeyUsageOID, ExtensionOID, NameOID, ObjectIdentifier, SignatureAlgorithmOID, _SIG_OIDS_TO_HASH ) @@ -95,9 +95,9 @@ OID_ANY_POLICY = CertificatePoliciesOID.ANY_POLICY OID_CPS_QUALIFIER = CertificatePoliciesOID.CPS_QUALIFIER OID_CPS_USER_NOTICE = CertificatePoliciesOID.CPS_USER_NOTICE -OID_CERTIFICATE_ISSUER = CRLExtensionOID.CERTIFICATE_ISSUER -OID_CRL_REASON = CRLExtensionOID.CRL_REASON -OID_INVALIDITY_DATE = CRLExtensionOID.INVALIDITY_DATE +OID_CERTIFICATE_ISSUER = RevokedExtensionOID.CERTIFICATE_ISSUER +OID_CRL_REASON = RevokedExtensionOID.CRL_REASON +OID_INVALIDITY_DATE = RevokedExtensionOID.INVALIDITY_DATE OID_CA_ISSUERS = AuthorityInformationAccessOID.CA_ISSUERS OID_OCSP = AuthorityInformationAccessOID.OCSP diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py index 9dc49a60..01eadfcb 100644 --- a/src/cryptography/x509/base.py +++ b/src/cryptography/x509/base.py @@ -176,12 +176,6 @@ class CertificateRevocationList(object): Returns the date of last update for this CRL. """ - @abc.abstractproperty - def revoked_certificates(self): - """ - Returns a list of RevokedCertificate objects for this CRL. - """ - @abc.abstractproperty def extensions(self): """ diff --git a/src/cryptography/x509/oid.py b/src/cryptography/x509/oid.py index ead40169..667045af 100644 --- a/src/cryptography/x509/oid.py +++ b/src/cryptography/x509/oid.py @@ -58,7 +58,7 @@ class ExtensionOID(object): OCSP_NO_CHECK = ObjectIdentifier("1.3.6.1.5.5.7.48.1.5") -class CRLExtensionOID(object): +class RevokedExtensionOID(object): CERTIFICATE_ISSUER = ObjectIdentifier("2.5.29.29") CRL_REASON = ObjectIdentifier("2.5.29.21") INVALIDITY_DATE = ObjectIdentifier("2.5.29.24") @@ -177,9 +177,9 @@ _OID_NAMES = { ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName", ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName", ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints", - CRLExtensionOID.CRL_REASON: "cRLReason", - CRLExtensionOID.INVALIDITY_DATE: "invalidityDate", - CRLExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", + RevokedExtensionOID.CRL_REASON: "cRLReason", + RevokedExtensionOID.INVALIDITY_DATE: "invalidityDate", + RevokedExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", ExtensionOID.NAME_CONSTRAINTS: "nameConstraints", ExtensionOID.CRL_DISTRIBUTION_POINTS: "cRLDistributionPoints", ExtensionOID.CERTIFICATE_POLICIES: "certificatePolicies", diff --git a/tests/test_x509.py b/tests/test_x509.py index 61e7a7d0..347ed1a6 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -153,17 +153,19 @@ class TestCertificateRevocationList(object): assert crl.next_update.isoformat() == "2016-01-01T00:00:00" assert crl.last_update.isoformat() == "2015-01-01T00:00:00" - def test_revoked_certs(self, backend): + def test_revoked_cert_retrieval(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, backend ) - assert isinstance(crl.revoked_certificates, list) - for r in crl.revoked_certificates: + for r in crl: assert isinstance(r, x509.RevokedCertificate) + # Check that len() works for CRLs. + assert len(crl) == 12 + def test_extensions(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), @@ -186,7 +188,7 @@ class TestRevokedCertificate(object): backend ) - for i, rev in enumerate(crl.revoked_certificates): + for i, rev in enumerate(crl): assert isinstance(rev, x509.RevokedCertificate) assert isinstance(rev.serial_number, int) assert isinstance(rev.revocation_date, datetime.datetime) @@ -204,7 +206,7 @@ class TestRevokedCertificate(object): # First revoked cert doesn't have extensions, test if it is handled # correctly. - rev0 = crl.revoked_certificates[0] + rev0 = crl[0] # It should return an empty Extensions object. assert isinstance(rev0.extensions, x509.Extensions) assert len(rev0.extensions) == 0 @@ -216,7 +218,7 @@ class TestRevokedCertificate(object): assert rev0.get_reason() is None # Test manual retrieval of extension values. - rev1 = crl.revoked_certificates[1] + rev1 = crl[1] assert isinstance(rev1.extensions, x509.Extensions) reason = rev1.extensions.get_extension_for_oid( @@ -232,18 +234,11 @@ class TestRevokedCertificate(object): assert rev1.get_invalidity_date().isoformat() == "2015-01-01T00:00:00" # Check if all reason flags can be found in the CRL. - # Also test if CRL as iterator works. flags = set(x509.ReasonFlags) for r in crl: flags.discard(r.get_reason()) assert len(flags) == 0 - # Check that len() works for CRLs. - assert len(crl) == 12 - - # Check that direct access to revoked cert in CRL works - assert isinstance(crl[0], x509.RevokedCertificate) - def test_duplicate_entry_ext(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_dup_entry_ext.pem"), @@ -252,7 +247,7 @@ class TestRevokedCertificate(object): ) with pytest.raises(x509.DuplicateExtension): - crl.revoked_certificates[0].extensions + crl[0].extensions def test_unsupported_crit_entry_ext(self, backend): crl = _load_cert( @@ -264,7 +259,7 @@ class TestRevokedCertificate(object): ) with pytest.raises(x509.UnsupportedExtension): - crl.revoked_certificates[0].extensions + crl[0].extensions def test_unsupported_reason(self, backend): crl = _load_cert( @@ -276,7 +271,7 @@ class TestRevokedCertificate(object): ) with pytest.raises(ValueError): - crl.revoked_certificates[0].extensions + crl[0].extensions def test_cert_issuer_ext(self, backend): if backend._lib.OPENSSL_VERSION_NUMBER < 0x10000000: @@ -295,7 +290,7 @@ class TestRevokedCertificate(object): ])) ]) - rev = crl.revoked_certificates[1] + rev = crl[1] issuer = rev.extensions.get_extension_for_oid( x509.OID_CERTIFICATE_ISSUER).value assert issuer == exp_issuer -- cgit v1.2.3 From 164bae538cfe5fcb320ebe5ee7e080598ad7ec5f Mon Sep 17 00:00:00 2001 From: Erik Trauschke Date: Wed, 14 Oct 2015 09:23:41 -0700 Subject: import fix --- src/cryptography/x509/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py index 697df6f2..6438da9c 100644 --- a/src/cryptography/x509/__init__.py +++ b/src/cryptography/x509/__init__.py @@ -28,8 +28,8 @@ from cryptography.x509.general_name import ( ) from cryptography.x509.name import Name, NameAttribute from cryptography.x509.oid import ( - AuthorityInformationAccessOID, RevokedExtensionOID, CertificatePoliciesOID, - ExtendedKeyUsageOID, ExtensionOID, NameOID, ObjectIdentifier, + AuthorityInformationAccessOID, CertificatePoliciesOID, ExtendedKeyUsageOID, + ExtensionOID, NameOID, ObjectIdentifier, RevokedExtensionOID, SignatureAlgorithmOID, _SIG_OIDS_TO_HASH ) -- cgit v1.2.3 From d4e7d43416077f18a37008298abdc566bd3f069d Mon Sep 17 00:00:00 2001 From: Erik Trauschke Date: Thu, 15 Oct 2015 14:45:38 -0700 Subject: removing caching mechanism for x509 properties undo name change of CRLExtensionOID use custom parsing mechanism for certIssuer entry extension add new crl to vectors for testing invalid certIssuer entry ext --- src/_cffi_src/openssl/x509v3.py | 2 + src/cryptography/hazmat/backends/openssl/x509.py | 107 ++++++++------------- src/cryptography/x509/__init__.py | 10 +- src/cryptography/x509/oid.py | 8 +- tests/test_x509.py | 37 ++++--- .../custom/crl_inval_cert_issuer_entry_ext.pem | 11 +++ 6 files changed, 80 insertions(+), 95 deletions(-) create mode 100644 vectors/cryptography_vectors/x509/custom/crl_inval_cert_issuer_entry_ext.pem diff --git a/src/_cffi_src/openssl/x509v3.py b/src/_cffi_src/openssl/x509v3.py index 84e49640..51cac62b 100644 --- a/src/_cffi_src/openssl/x509v3.py +++ b/src/_cffi_src/openssl/x509v3.py @@ -290,6 +290,8 @@ DIST_POINT_NAME *DIST_POINT_NAME_new(void); void DIST_POINT_NAME_free(DIST_POINT_NAME *); int i2d_CRL_DIST_POINTS(Cryptography_STACK_OF_DIST_POINT *, unsigned char **); +GENERAL_NAMES *d2i_GENERAL_NAMES(GENERAL_NAMES **, const unsigned char **, + long); """ CUSTOMIZATIONS = """ diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index 7f7be545..073dfb1e 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -19,7 +19,7 @@ from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives import hashes, serialization from cryptography.x509.oid import ( - CertificatePoliciesOID, ExtensionOID, RevokedExtensionOID + CRLExtensionOID, CertificatePoliciesOID, ExtensionOID ) @@ -175,11 +175,11 @@ def _decode_ocsp_no_check(backend, ext): class _X509ExtensionParser(object): - def __init__(self, ext_count, get_ext, handlers, supported_versions=None): + 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.supported_versions = supported_versions + self.unsupported_exts = unsupported_exts def parse(self, backend, x509_obj): extensions = [] @@ -190,13 +190,6 @@ class _X509ExtensionParser(object): crit = backend._lib.X509_EXTENSION_get_critical(ext) critical = crit == 1 oid = x509.ObjectIdentifier(_obj2txt(backend, ext.object)) - - # Filter out extensions we know are not supported by the backend - if (self.supported_versions and oid in self.supported_versions and - self.supported_versions[oid] > - backend._lib.OPENSSL_VERSION_NUMBER): - self.handlers.pop(oid, None) - if oid in seen_oids: raise x509.DuplicateExtension( "Duplicate {0} extension found".format(oid), oid @@ -210,15 +203,18 @@ class _X509ExtensionParser(object): .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) + 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) @@ -687,8 +683,18 @@ def _decode_invalidity_date(backend, inv_date): return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ") -def _decode_cert_issuer(backend, issuer): - gns = backend._ffi.cast("GENERAL_NAMES *", issuer) +def _decode_cert_issuer(backend, ext): + 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 + ) + 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)) @@ -699,28 +705,16 @@ class _RevokedCertificate(object): self._backend = backend self._x509_revoked = x509_revoked - self._serial_number = None - self._revocation_date = None - self._extensions = None - @property def serial_number(self): - if self._serial_number: - return self._serial_number - asn1_int = self._x509_revoked.serialNumber self._backend.openssl_assert(asn1_int != self._backend._ffi.NULL) - self._serial_number = self._backend._asn1_integer_to_int(asn1_int) - return self._serial_number + return self._backend._asn1_integer_to_int(asn1_int) @property def revocation_date(self): - if self._revocation_date: - return self._revocation_date - - self._revocation_date = self._backend._parse_asn1_time( + return self._backend._parse_asn1_time( self._x509_revoked.revocationDate) - return self._revocation_date @property def extensions(self): @@ -765,11 +759,6 @@ class _CertificateRevocationList(object): self._backend = backend self._x509_crl = x509_crl - self._revoked = None - self._issuer = None - self._next_update = None - self._last_update = None - def __eq__(self, other): if not isinstance(other, x509.CertificateRevocationList): return NotImplemented @@ -803,38 +792,23 @@ class _CertificateRevocationList(object): @property def issuer(self): - if self._issuer: - return self._issuer - issuer = self._backend._lib.X509_CRL_get_issuer(self._x509_crl) self._backend.openssl_assert(issuer != self._backend._ffi.NULL) - self._issuer = _decode_x509_name(self._backend, issuer) - return self._issuer + return _decode_x509_name(self._backend, issuer) @property def next_update(self): - if self._next_update: - return self._next_update - nu = self._backend._lib.X509_CRL_get_nextUpdate(self._x509_crl) self._backend.openssl_assert(nu != self._backend._ffi.NULL) - self._next_update = self._backend._parse_asn1_time(nu) - return self._next_update + return self._backend._parse_asn1_time(nu) @property def last_update(self): - if self._last_update: - return self._last_update - lu = self._backend._lib.X509_CRL_get_lastUpdate(self._x509_crl) self._backend.openssl_assert(lu != self._backend._ffi.NULL) - self._last_update = self._backend._parse_asn1_time(lu) - return self._last_update + return self._backend._parse_asn1_time(lu) def _revoked_certificates(self): - if self._revoked: - return self._revoked - revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl) self._backend.openssl_assert(revoked != self._backend._ffi.NULL) @@ -845,8 +819,7 @@ class _CertificateRevocationList(object): self._backend.openssl_assert(r != self._backend._ffi.NULL) revoked_list.append(_RevokedCertificate(self._backend, r)) - self._revoked = revoked_list - return self._revoked + return revoked_list def __iter__(self): return iter(self._revoked_certificates()) @@ -943,14 +916,14 @@ _EXTENSION_HANDLERS = { } _REVOKED_EXTENSION_HANDLERS = { - RevokedExtensionOID.CRL_REASON: _decode_crl_reason, - RevokedExtensionOID.INVALIDITY_DATE: _decode_invalidity_date, - RevokedExtensionOID.CERTIFICATE_ISSUER: _decode_cert_issuer, + CRLExtensionOID.CRL_REASON: _decode_crl_reason, + CRLExtensionOID.INVALIDITY_DATE: _decode_invalidity_date, + CRLExtensionOID.CERTIFICATE_ISSUER: _decode_cert_issuer, } -_REVOKED_SUPPORTED_VERSIONS = { - RevokedExtensionOID.CERTIFICATE_ISSUER: 0x10000000, -} +_REVOKED_UNSUPPORTED_EXTENSIONS = set([ + CRLExtensionOID.CERTIFICATE_ISSUER, +]) _CERTIFICATE_EXTENSION_PARSER = _X509ExtensionParser( ext_count=lambda backend, x: backend._lib.X509_get_ext_count(x), @@ -968,5 +941,5 @@ _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, - supported_versions=_REVOKED_SUPPORTED_VERSIONS + unsupported_exts=_REVOKED_UNSUPPORTED_EXTENSIONS ) diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py index 6438da9c..70e1d3da 100644 --- a/src/cryptography/x509/__init__.py +++ b/src/cryptography/x509/__init__.py @@ -28,8 +28,8 @@ from cryptography.x509.general_name import ( ) from cryptography.x509.name import Name, NameAttribute from cryptography.x509.oid import ( - AuthorityInformationAccessOID, CertificatePoliciesOID, ExtendedKeyUsageOID, - ExtensionOID, NameOID, ObjectIdentifier, RevokedExtensionOID, + AuthorityInformationAccessOID, CRLExtensionOID, CertificatePoliciesOID, + ExtendedKeyUsageOID, ExtensionOID, NameOID, ObjectIdentifier, SignatureAlgorithmOID, _SIG_OIDS_TO_HASH ) @@ -95,9 +95,9 @@ OID_ANY_POLICY = CertificatePoliciesOID.ANY_POLICY OID_CPS_QUALIFIER = CertificatePoliciesOID.CPS_QUALIFIER OID_CPS_USER_NOTICE = CertificatePoliciesOID.CPS_USER_NOTICE -OID_CERTIFICATE_ISSUER = RevokedExtensionOID.CERTIFICATE_ISSUER -OID_CRL_REASON = RevokedExtensionOID.CRL_REASON -OID_INVALIDITY_DATE = RevokedExtensionOID.INVALIDITY_DATE +OID_CERTIFICATE_ISSUER = CRLExtensionOID.CERTIFICATE_ISSUER +OID_CRL_REASON = CRLExtensionOID.CRL_REASON +OID_INVALIDITY_DATE = CRLExtensionOID.INVALIDITY_DATE OID_CA_ISSUERS = AuthorityInformationAccessOID.CA_ISSUERS OID_OCSP = AuthorityInformationAccessOID.OCSP diff --git a/src/cryptography/x509/oid.py b/src/cryptography/x509/oid.py index 667045af..ead40169 100644 --- a/src/cryptography/x509/oid.py +++ b/src/cryptography/x509/oid.py @@ -58,7 +58,7 @@ class ExtensionOID(object): OCSP_NO_CHECK = ObjectIdentifier("1.3.6.1.5.5.7.48.1.5") -class RevokedExtensionOID(object): +class CRLExtensionOID(object): CERTIFICATE_ISSUER = ObjectIdentifier("2.5.29.29") CRL_REASON = ObjectIdentifier("2.5.29.21") INVALIDITY_DATE = ObjectIdentifier("2.5.29.24") @@ -177,9 +177,9 @@ _OID_NAMES = { ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName", ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName", ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints", - RevokedExtensionOID.CRL_REASON: "cRLReason", - RevokedExtensionOID.INVALIDITY_DATE: "invalidityDate", - RevokedExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", + CRLExtensionOID.CRL_REASON: "cRLReason", + CRLExtensionOID.INVALIDITY_DATE: "invalidityDate", + CRLExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", ExtensionOID.NAME_CONSTRAINTS: "nameConstraints", ExtensionOID.CRL_DISTRIBUTION_POINTS: "cRLDistributionPoints", ExtensionOID.CERTIFICATE_POLICIES: "certificatePolicies", diff --git a/tests/test_x509.py b/tests/test_x509.py index 347ed1a6..ded2f0ee 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -204,6 +204,13 @@ class TestRevokedCertificate(object): backend ) + exp_issuer = x509.GeneralNames([ + x509.DirectoryName(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), + x509.NameAttribute(x509.OID_COMMON_NAME, u"cryptography.io"), + ])) + ]) + # First revoked cert doesn't have extensions, test if it is handled # correctly. rev0 = crl[0] @@ -225,6 +232,10 @@ class TestRevokedCertificate(object): x509.OID_CRL_REASON).value assert reason == x509.ReasonFlags.unspecified + issuer = rev1.extensions.get_extension_for_oid( + x509.OID_CERTIFICATE_ISSUER).value + assert issuer == exp_issuer + date = rev1.extensions.get_extension_for_oid( x509.OID_INVALIDITY_DATE).value assert isinstance(date, datetime.datetime) @@ -232,6 +243,7 @@ class TestRevokedCertificate(object): # Test convenience function. assert rev1.get_invalidity_date().isoformat() == "2015-01-01T00:00:00" + assert rev1.get_certificate_issuer() == exp_issuer # Check if all reason flags can be found in the CRL. flags = set(x509.ReasonFlags) @@ -273,30 +285,17 @@ class TestRevokedCertificate(object): with pytest.raises(ValueError): crl[0].extensions - def test_cert_issuer_ext(self, backend): - if backend._lib.OPENSSL_VERSION_NUMBER < 0x10000000: - pytest.skip("Requires a newer OpenSSL. Must be at least 1.0.0") - + def test_invalid_cert_issuer_ext(self, backend): crl = _load_cert( - os.path.join("x509", "custom", "crl_all_reasons.pem"), + os.path.join( + "x509", "custom", "crl_inval_cert_issuer_entry_ext.pem" + ), x509.load_pem_x509_crl, backend ) - exp_issuer = x509.GeneralNames([ - x509.DirectoryName(x509.Name([ - x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), - x509.NameAttribute(x509.OID_COMMON_NAME, u"cryptography.io"), - ])) - ]) - - rev = crl[1] - issuer = rev.extensions.get_extension_for_oid( - x509.OID_CERTIFICATE_ISSUER).value - assert issuer == exp_issuer - - # Test convenience function. - assert rev.get_certificate_issuer() == exp_issuer + with pytest.raises(ValueError): + crl[0].extensions @pytest.mark.requires_backend_interface(interface=RSABackend) diff --git a/vectors/cryptography_vectors/x509/custom/crl_inval_cert_issuer_entry_ext.pem b/vectors/cryptography_vectors/x509/custom/crl_inval_cert_issuer_entry_ext.pem new file mode 100644 index 00000000..a54f2409 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/crl_inval_cert_issuer_entry_ext.pem @@ -0,0 +1,11 @@ +-----BEGIN X509 CRL----- +MIIBlzCBgAIBAjANBgkqhkiG9w0BAQsFADAnMQswCQYDVQQGEwJVUzEYMBYGA1UE +AwwPY3J5cHRvZ3JhcGh5LmlvGA8yMDE1MDEwMTAwMDAwMFoYDzIwMTYwMTAxMDAw +MDAwWjAhMB8CAQAYDzIwMTUwMTAxMDAwMDAwWjAJMAcGA1UdHQQAMA0GCSqGSIb3 +DQEBCwUAA4IBAQCRSNP2LfnpubvOrZ8/UsETlVTvMNc38xM6dqzYKQV8vN+fcMXP +1z/nTMBGNvnp7u7S+Dx/1Klq/iArtP5oOdNDeVuapfUdDgFJryXkvgX+2B0g/l9+ +/fiH9YNTEG6Yj2XC3lsVwXhid1Sx+A+b8ZXBdyjyZSJSoejPhzO5p/SQAk+ahY3I +FZeL3CXlUUi1v3MtVLBPUQZvepZ9mIv5uRsEmHXFf6uTLmMdV/j7cQn4/K53Qb1N +e+2WpNJOv0UIDugn2DKACF108T1YgZTcx2F4TYpnVDDkyjK4J1IMBrNie8hWA/R3 +y/9oP0PihPDhi4jcVBpDW7pRPqee+4z1KqXg +-----END X509 CRL----- -- cgit v1.2.3