diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ab6468d8299d..4052ff50e8c9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,7 +15,7 @@ Changelog are: * :class:`~cryptography.x509.CertificateIssuer` - * ``CRLReason`` + * :class:`~cryptography.x509.CRLReason` * ``InvalidityDate`` * The :class:`~cryptography.x509.Certificate` class now has :attr:`~cryptography.x509.Certificate.signature` and diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 1f25ac14a03c..51de07475b8d 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -908,7 +908,7 @@ X.509 Revoked Certificate Object >>> for ext in revoked_certificate.extensions: ... print(ext) , critical=False, value=2015-01-01 00:00:00)> - , critical=False, value=ReasonFlags.key_compromise)> + , critical=False, value=)> X.509 Revoked Certificate Builder ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1989,6 +1989,28 @@ These extensions are only valid within a :class:`RevokedCertificate` object. :returns: A list of values extracted from the matched general names. The type of the returned values depends on the :class:`GeneralName`. +.. class:: CRLReason(reason) + + .. versionadded:: 1.2 + + CRL reason (also known as ``reasonCode``) is an extension that is only + valid inside :class:`~cryptography.x509.RevokedCertificate` objects. It + identifies a reason for the certificate revocation. + + :param reason: A value from the + :class:`~cryptography.x509.oid.CRLEntryExtensionOID` enum. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.CRLEntryExtensionOID.CRL_REASON`. + + .. attribute:: reason + + :type: An element from :class:`~cryptography.x509.ReasonFlags` + Object Identifiers ~~~~~~~~~~~~~~~~~~ diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index 05390809694c..2650b5d471b9 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -700,7 +700,7 @@ def _decode_crl_reason(backend, enum): code = backend._lib.ASN1_ENUMERATED_get(enum) try: - return _CRL_REASON_CODE_TO_ENUM[code] + return x509.CRLReason(_CRL_REASON_CODE_TO_ENUM[code]) except KeyError: raise ValueError("Unsupported reason code: {0}".format(code)) diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py index 9946daa004d7..89e7f063e642 100644 --- a/src/cryptography/x509/__init__.py +++ b/src/cryptography/x509/__init__.py @@ -15,7 +15,7 @@ from cryptography.x509.extensions import ( AccessDescription, AuthorityInformationAccess, AuthorityKeyIdentifier, BasicConstraints, CRLDistributionPoints, - CRLNumber, CertificateIssuer, CertificatePolicies, + CRLNumber, CRLReason, CertificateIssuer, CertificatePolicies, DistributionPoint, DuplicateExtension, ExtendedKeyUsage, Extension, ExtensionNotFound, ExtensionType, Extensions, GeneralNames, InhibitAnyPolicy, IssuerAlternativeName, KeyUsage, @@ -167,4 +167,5 @@ "_GENERAL_NAMES", "CRLExtensionOID", "CertificateIssuer", + "CRLReason", ] diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py index 3c017ea1e040..6ae00927a428 100644 --- a/src/cryptography/x509/extensions.py +++ b/src/cryptography/x509/extensions.py @@ -976,3 +976,28 @@ def __eq__(self, other): def __ne__(self, other): return not self == other + + +@utils.register_interface(ExtensionType) +class CRLReason(object): + oid = CRLEntryExtensionOID.CRL_REASON + + def __init__(self, reason): + if not isinstance(reason, ReasonFlags): + raise TypeError("reason must be an element from ReasonFlags") + + self._reason = reason + + def __repr__(self): + return "".format(self._reason) + + def __eq__(self, other): + if not isinstance(other, CRLReason): + return NotImplemented + + return self.reason == other.reason + + def __ne__(self, other): + return not self == other + + reason = utils.read_only_property("_reason") diff --git a/tests/test_x509.py b/tests/test_x509.py index c91f08ba72fc..757df442b83f 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -379,9 +379,9 @@ def test_revoked_extensions(self, backend): 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 + reason = rev1.extensions.get_extension_for_class( + x509.CRLReason).value + assert reason == x509.CRLReason(x509.ReasonFlags.unspecified) issuer = rev1.extensions.get_extension_for_class( x509.CertificateIssuer).value @@ -395,12 +395,12 @@ def test_revoked_extensions(self, backend): flags = set(x509.ReasonFlags) for rev in crl: try: - r = rev.extensions.get_extension_for_oid(x509.OID_CRL_REASON) + r = rev.extensions.get_extension_for_class(x509.CRLReason) except x509.ExtensionNotFound: # Not all revoked certs have a reason extension. pass else: - flags.discard(r.value) + flags.discard(r.value.reason) assert len(flags) == 0 diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py index 037512a499c2..b8105a4bf14a 100644 --- a/tests/test_x509_ext.py +++ b/tests/test_x509_ext.py @@ -112,6 +112,29 @@ def test_get_values_for_type(self): assert names == [u"cryptography.io"] +class TestCRLReason(object): + def test_invalid_reason_flags(self): + with pytest.raises(TypeError): + x509.CRLReason("notareason") + + def test_eq(self): + reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) + reason2 = x509.CRLReason(x509.ReasonFlags.unspecified) + assert reason1 == reason2 + + def test_ne(self): + reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) + reason2 = x509.CRLReason(x509.ReasonFlags.ca_compromise) + assert reason1 != reason2 + assert reason1 != object() + + def test_repr(self): + reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) + assert repr(reason1) == ( + "" + ) + + class TestNoticeReference(object): def test_notice_numbers_not_all_int(self): with pytest.raises(TypeError):