aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/x509/reference.rst111
-rw-r--r--src/cryptography/hazmat/backends/multibackend.py18
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py26
-rw-r--r--src/cryptography/hazmat/backends/openssl/x509.py258
-rw-r--r--src/cryptography/x509/__init__.py6
-rw-r--r--src/cryptography/x509/base.py8
-rw-r--r--tests/hazmat/backends/test_multibackend.py12
-rw-r--r--tests/test_x509.py246
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 a8f639d5..c4cac1a6 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 import binding
from cryptography.hazmat.primitives import hashes, serialization
@@ -1467,6 +1467,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 80f32e29..8a13aa5d 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):