aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/x509/reference.rst125
-rw-r--r--src/_cffi_src/openssl/x509v3.py2
-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.py238
-rw-r--r--src/cryptography/x509/__init__.py6
-rw-r--r--src/cryptography/x509/base.py14
-rw-r--r--tests/hazmat/backends/test_multibackend.py12
-rw-r--r--tests/test_x509.py246
-rw-r--r--vectors/cryptography_vectors/x509/custom/crl_inval_cert_issuer_entry_ext.pem11
10 files changed, 672 insertions, 26 deletions
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst
index 87383db1..eec0cbcc 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -340,6 +401,20 @@ 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])
+ <class 'cryptography.hazmat.backends.openssl.x509._RevokedCertificate'>
+ >>> for r in crl:
+ ... print(r.serial_number)
+ 0
+
.. method:: fingerprint(algorithm)
:param algorithm: The
@@ -349,6 +424,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 +438,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,17 +462,21 @@ 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.
- .. attribute:: revoked_certificates
-
- :type: list of :class:`RevokedCertificate`
+ .. doctest::
- The revoked certificates listed in this CRL.
+ >>> crl.last_update
+ datetime.datetime(2015, 1, 1, 0, 0)
.. attribute:: extensions
@@ -603,24 +699,45 @@ X.509 Revoked Certificate Object
.. versionadded:: 1.0
+ .. doctest::
+
+ >>> revoked_certificate = crl[0]
+
.. attribute:: serial_number
:type: :class:`int`
An integer representing the serial number of the revoked certificate.
+ .. doctest::
+
+ >>> revoked_certificate.serial_number
+ 0
+
.. attribute:: revocation_date
:type: :class:`datetime.datetime`
A naïve datetime representing the date this certificates was revoked.
+ .. doctest::
+
+ >>> revoked_certificate.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 revoked_certificate.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/_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/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 ac025e95..06db6f22 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -37,8 +37,8 @@ from cryptography.hazmat.backends.openssl.rsa import (
_RSAPrivateKey, _RSAPublicKey
)
from cryptography.hazmat.backends.openssl.x509 import (
- _Certificate, _CertificateSigningRequest, _DISTPOINT_TYPE_FULLNAME,
- _DISTPOINT_TYPE_RELATIVENAME
+ _Certificate, _CertificateRevocationList, _CertificateSigningRequest,
+ _DISTPOINT_TYPE_FULLNAME, _DISTPOINT_TYPE_RELATIVENAME
)
from cryptography.hazmat.bindings.openssl import binding
from cryptography.hazmat.primitives import hashes, serialization
@@ -1452,6 +1452,28 @@ class Backend(object):
x509 = self._ffi.gc(x509, self._lib.X509_free)
return _Certificate(self, x509)
+ def load_pem_x509_crl(self, data):
+ mem_bio = self._bytes_to_bio(data)
+ x509_crl = self._lib.PEM_read_bio_X509_CRL(
+ mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL
+ )
+ if x509_crl == self._ffi.NULL:
+ self._consume_errors()
+ raise ValueError("Unable to load CRL")
+
+ x509_crl = self._ffi.gc(x509_crl, self._lib.X509_CRL_free)
+ return _CertificateRevocationList(self, x509_crl)
+
+ def load_der_x509_crl(self, data):
+ mem_bio = self._bytes_to_bio(data)
+ x509_crl = self._lib.d2i_X509_CRL_bio(mem_bio.bio, self._ffi.NULL)
+ if x509_crl == self._ffi.NULL:
+ self._consume_errors()
+ raise ValueError("Unable to load CRL")
+
+ x509_crl = self._ffi.gc(x509_crl, self._lib.X509_CRL_free)
+ return _CertificateRevocationList(self, x509_crl)
+
def load_pem_x509_csr(self, data):
mem_bio = self._bytes_to_bio(data)
x509_req = self._lib.PEM_read_bio_X509_REQ(
diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py
index 2de5a8c7..7ca4850d 100644
--- a/src/cryptography/hazmat/backends/openssl/x509.py
+++ b/src/cryptography/hazmat/backends/openssl/x509.py
@@ -4,7 +4,9 @@
from __future__ import absolute_import, division, print_function
+import datetime
import ipaddress
+
from email.utils import parseaddr
import idna
@@ -16,7 +18,9 @@ from six.moves import urllib_parse
from cryptography import utils, x509
from cryptography.exceptions import UnsupportedAlgorithm
from cryptography.hazmat.primitives import hashes, serialization
-from cryptography.x509.oid import CertificatePoliciesOID, ExtensionOID
+from cryptography.x509.oid import (
+ CRLExtensionOID, CertificatePoliciesOID, ExtensionOID
+)
def _obj2txt(backend, obj):
@@ -176,10 +180,11 @@ def _decode_ocsp_no_check(backend, ext):
class _X509ExtensionParser(object):
- def __init__(self, ext_count, get_ext, handlers):
+ def __init__(self, ext_count, get_ext, handlers, unsupported_exts=None):
self.ext_count = ext_count
self.get_ext = get_ext
self.handlers = handlers
+ self.unsupported_exts = unsupported_exts
def parse(self, backend, x509_obj):
extensions = []
@@ -199,18 +204,22 @@ class _X509ExtensionParser(object):
except KeyError:
if critical:
raise x509.UnsupportedExtension(
- "{0} is not currently supported".format(oid), oid
+ "Critical extension {0} is not currently supported"
+ .format(oid), oid
)
else:
- d2i = backend._lib.X509V3_EXT_d2i(ext)
- if d2i == backend._ffi.NULL:
- backend._consume_errors()
- raise ValueError(
- "The {0} extension is invalid and can't be "
- "parsed".format(oid)
- )
-
- value = handler(backend, d2i)
+ if self.unsupported_exts and oid in self.unsupported_exts:
+ ext_data = ext
+ else:
+ ext_data = backend._lib.X509V3_EXT_d2i(ext)
+ if ext_data == backend._ffi.NULL:
+ backend._consume_errors()
+ raise ValueError(
+ "The {0} extension is invalid and can't be "
+ "parsed".format(oid)
+ )
+
+ value = handler(backend, ext_data)
extensions.append(x509.Extension(oid, critical, value))
seen_oids.add(oid)
@@ -646,6 +655,195 @@ def _decode_inhibit_any_policy(backend, asn1_int):
return x509.InhibitAnyPolicy(skip_certs)
+def _decode_crl_reason(backend, enum):
+ enum = backend._ffi.cast("ASN1_ENUMERATED *", enum)
+ enum = backend._ffi.gc(enum, backend._lib.ASN1_ENUMERATED_free)
+ code = backend._lib.ASN1_ENUMERATED_get(enum)
+
+ try:
+ return {
+ 0: x509.ReasonFlags.unspecified,
+ 1: x509.ReasonFlags.key_compromise,
+ 2: x509.ReasonFlags.ca_compromise,
+ 3: x509.ReasonFlags.affiliation_changed,
+ 4: x509.ReasonFlags.superseded,
+ 5: x509.ReasonFlags.cessation_of_operation,
+ 6: x509.ReasonFlags.certificate_hold,
+ 8: x509.ReasonFlags.remove_from_crl,
+ 9: x509.ReasonFlags.privilege_withdrawn,
+ 10: x509.ReasonFlags.aa_compromise,
+ }[code]
+ except KeyError:
+ raise ValueError("Unsupported reason code: {0}".format(code))
+
+
+def _decode_invalidity_date(backend, inv_date):
+ generalized_time = backend._ffi.cast(
+ "ASN1_GENERALIZEDTIME *", inv_date
+ )
+ generalized_time = backend._ffi.gc(
+ generalized_time, backend._lib.ASN1_GENERALIZEDTIME_free
+ )
+ time = backend._ffi.string(
+ backend._lib.ASN1_STRING_data(
+ backend._ffi.cast("ASN1_STRING *", generalized_time)
+ )
+ ).decode("ascii")
+ return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ")
+
+
+def _decode_cert_issuer(backend, ext):
+ 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))
+
+
+@utils.register_interface(x509.RevokedCertificate)
+class _RevokedCertificate(object):
+ def __init__(self, backend, x509_revoked):
+ self._backend = backend
+ self._x509_revoked = x509_revoked
+
+ @property
+ def serial_number(self):
+ asn1_int = self._x509_revoked.serialNumber
+ self._backend.openssl_assert(asn1_int != self._backend._ffi.NULL)
+ return self._backend._asn1_integer_to_int(asn1_int)
+
+ @property
+ def revocation_date(self):
+ return self._backend._parse_asn1_time(
+ self._x509_revoked.revocationDate)
+
+ @property
+ def extensions(self):
+ return _REVOKED_CERTIFICATE_EXTENSION_PARSER.parse(
+ self._backend, self._x509_revoked
+ )
+
+ 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
+
+
+@utils.register_interface(x509.CertificateRevocationList)
+class _CertificateRevocationList(object):
+ def __init__(self, backend, x509_crl):
+ self._backend = backend
+ self._x509_crl = x509_crl
+
+ def __eq__(self, other):
+ if not isinstance(other, x509.CertificateRevocationList):
+ return NotImplemented
+
+ res = self._backend._lib.X509_CRL_cmp(self._x509_crl, other._x509_crl)
+ return res == 0
+
+ def __ne__(self, other):
+ return not self == other
+
+ def fingerprint(self, algorithm):
+ h = hashes.Hash(algorithm, self._backend)
+ bio = self._backend._create_mem_bio()
+ res = self._backend._lib.i2d_X509_CRL_bio(
+ bio, self._x509_crl
+ )
+ self._backend.openssl_assert(res == 1)
+ der = self._backend._read_mem_bio(bio)
+ h.update(der)
+ return h.finalize()
+
+ @property
+ def signature_hash_algorithm(self):
+ oid = _obj2txt(self._backend, self._x509_crl.sig_alg.algorithm)
+ try:
+ return x509._SIG_OIDS_TO_HASH[oid]
+ except KeyError:
+ raise UnsupportedAlgorithm(
+ "Signature algorithm OID:{0} not recognized".format(oid)
+ )
+
+ @property
+ def issuer(self):
+ issuer = self._backend._lib.X509_CRL_get_issuer(self._x509_crl)
+ self._backend.openssl_assert(issuer != self._backend._ffi.NULL)
+ return _decode_x509_name(self._backend, issuer)
+
+ @property
+ def next_update(self):
+ nu = self._backend._lib.X509_CRL_get_nextUpdate(self._x509_crl)
+ self._backend.openssl_assert(nu != self._backend._ffi.NULL)
+ return self._backend._parse_asn1_time(nu)
+
+ @property
+ def last_update(self):
+ lu = self._backend._lib.X509_CRL_get_lastUpdate(self._x509_crl)
+ self._backend.openssl_assert(lu != self._backend._ffi.NULL)
+ return self._backend._parse_asn1_time(lu)
+
+ def _revoked_certificates(self):
+ revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl)
+ self._backend.openssl_assert(revoked != self._backend._ffi.NULL)
+
+ num = self._backend._lib.sk_X509_REVOKED_num(revoked)
+ revoked_list = []
+ for i in range(num):
+ r = self._backend._lib.sk_X509_REVOKED_value(revoked, i)
+ self._backend.openssl_assert(r != self._backend._ffi.NULL)
+ revoked_list.append(_RevokedCertificate(self._backend, r))
+
+ return revoked_list
+
+ def __iter__(self):
+ return iter(self._revoked_certificates())
+
+ def __getitem__(self, idx):
+ return self._revoked_certificates()[idx]
+
+ def __len__(self):
+ return len(self._revoked_certificates())
+
+ @property
+ def extensions(self):
+ raise NotImplementedError()
+
+
@utils.register_interface(x509.CertificateSigningRequest)
class _CertificateSigningRequest(object):
def __init__(self, backend, x509_req):
@@ -726,6 +924,15 @@ _EXTENSION_HANDLERS = {
ExtensionOID.NAME_CONSTRAINTS: _decode_name_constraints,
}
+_REVOKED_EXTENSION_HANDLERS = {
+ CRLExtensionOID.CRL_REASON: _decode_crl_reason,
+ CRLExtensionOID.INVALIDITY_DATE: _decode_invalidity_date,
+ CRLExtensionOID.CERTIFICATE_ISSUER: _decode_cert_issuer,
+}
+
+_REVOKED_UNSUPPORTED_EXTENSIONS = set([
+ CRLExtensionOID.CERTIFICATE_ISSUER,
+])
_CERTIFICATE_EXTENSION_PARSER = _X509ExtensionParser(
ext_count=lambda backend, x: backend._lib.X509_get_ext_count(x),
@@ -738,3 +945,10 @@ _CSR_EXTENSION_PARSER = _X509ExtensionParser(
get_ext=lambda backend, x, i: backend._lib.sk_X509_EXTENSION_value(x, i),
handlers=_EXTENSION_HANDLERS
)
+
+_REVOKED_CERTIFICATE_EXTENSION_PARSER = _X509ExtensionParser(
+ ext_count=lambda backend, x: backend._lib.X509_REVOKED_get_ext_count(x),
+ get_ext=lambda backend, x, i: backend._lib.X509_REVOKED_get_ext(x, i),
+ handlers=_REVOKED_EXTENSION_HANDLERS,
+ unsupported_exts=_REVOKED_UNSUPPORTED_EXTENSIONS
+)
diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py
index 1aa2598b..70e1d3da 100644
--- a/src/cryptography/x509/__init__.py
+++ b/src/cryptography/x509/__init__.py
@@ -8,8 +8,8 @@ from cryptography.x509.base import (
Certificate, CertificateBuilder, CertificateRevocationList,
CertificateSigningRequest, CertificateSigningRequestBuilder,
InvalidVersion, RevokedCertificate,
- Version, load_der_x509_certificate, load_der_x509_csr,
- load_pem_x509_certificate, load_pem_x509_csr,
+ Version, load_der_x509_certificate, load_der_x509_crl, load_der_x509_csr,
+ load_pem_x509_certificate, load_pem_x509_crl, load_pem_x509_csr,
)
from cryptography.x509.extensions import (
AccessDescription, AuthorityInformationAccess,
@@ -108,6 +108,8 @@ __all__ = [
"load_der_x509_certificate",
"load_pem_x509_csr",
"load_der_x509_csr",
+ "load_pem_x509_crl",
+ "load_der_x509_crl",
"InvalidVersion",
"DuplicateExtension",
"UnsupportedExtension",
diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py
index 27eafac6..01eadfcb 100644
--- a/src/cryptography/x509/base.py
+++ b/src/cryptography/x509/base.py
@@ -40,6 +40,14 @@ def load_der_x509_csr(data, backend):
return backend.load_der_x509_csr(data)
+def load_pem_x509_crl(data, backend):
+ return backend.load_pem_x509_crl(data)
+
+
+def load_der_x509_crl(data, backend):
+ return backend.load_der_x509_crl(data)
+
+
class InvalidVersion(Exception):
def __init__(self, msg, parsed_version):
super(InvalidVersion, self).__init__(msg)
@@ -169,12 +177,6 @@ class CertificateRevocationList(object):
"""
@abc.abstractproperty
- def revoked_certificates(self):
- """
- Returns a list of RevokedCertificate objects for this CRL.
- """
-
- @abc.abstractproperty
def extensions(self):
"""
Returns an Extensions object containing a list of CRL extensions.
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 8035886c..b9304c37 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_cert_retrieval(self, backend):
+ crl = _load_cert(
+ os.path.join("x509", "custom", "crl_all_reasons.pem"),
+ x509.load_pem_x509_crl,
+ backend
+ )
+
+ 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"),
+ 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):
+ 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
+ )
+
+ 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]
+ # 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[1]
+ assert isinstance(rev1.extensions, x509.Extensions)
+
+ reason = rev1.extensions.get_extension_for_oid(
+ 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)
+ assert date.isoformat() == "2015-01-01T00:00:00"
+
+ # 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)
+ for r in crl:
+ 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[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[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[0].extensions
+
+ def test_invalid_cert_issuer_ext(self, backend):
+ crl = _load_cert(
+ os.path.join(
+ "x509", "custom", "crl_inval_cert_issuer_entry_ext.pem"
+ ),
+ x509.load_pem_x509_crl,
+ backend
+ )
+
+ with pytest.raises(ValueError):
+ crl[0].extensions
+
+
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
class TestRSACertificate(object):
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-----