diff options
-rw-r--r-- | docs/x509/reference.rst | 111 | ||||
-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 | 258 | ||||
-rw-r--r-- | src/cryptography/x509/__init__.py | 6 | ||||
-rw-r--r-- | src/cryptography/x509/base.py | 8 | ||||
-rw-r--r-- | tests/hazmat/backends/test_multibackend.py | 12 | ||||
-rw-r--r-- | 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 + <Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.6, name=countryName)>, value=u'US')>, <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, 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) + <Extension(oid=<ObjectIdentifier(oid=2.5.29.24, name=invalidityDate)>, critical=False, value=2015-01-01 00:00:00)> + <Extension(oid=<ObjectIdentifier(oid=2.5.29.21, name=cRLReason)>, 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()) @@ -502,6 +510,10 @@ class TestMultiBackend(object): 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): backend.load_der_x509_csr(b"reqdata") 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): |