Skip to content

Commit

Permalink
Merge pull request pyca#2575 from reaperhulk/crlentry-extensions-classes
Browse files Browse the repository at this point in the history
start switching the CRL entry extensions to be full-fledged classes
  • Loading branch information
alex committed Dec 26, 2015
2 parents 045fc7e + 49bb756 commit 0860ef6
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 12 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ Changelog

.. note:: This version is not yet released and is under active development.

* **BACKWARDS INCOMPATIBLE:**
:class:`~cryptography.x509.RevokedCertificate`
:attr:`~cryptography.x509.RevokedCertificate.extensions` now uses extension
classes rather than returning raw values inside the
:class:`~cryptography.x509.Extension`
:attr:`~cryptography.x509.Extension.value`. The new classes
are:

* :class:`~cryptography.x509.CertificateIssuer`
* ``CRLReason``
* ``InvalidityDate``
* The :class:`~cryptography.x509.Certificate` class now has
:attr:`~cryptography.x509.Certificate.signature` and
:attr:`~cryptography.x509.Certificate.tbs_certificate_bytes` attributes.
Expand Down
34 changes: 34 additions & 0 deletions docs/x509/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1956,6 +1956,40 @@ These classes may be present within a :class:`CertificatePolicies` instance.

A list of integers.

CRL Entry Extensions
~~~~~~~~~~~~~~~~~~~~

These extensions are only valid within a :class:`RevokedCertificate` object.

.. class:: CertificateIssuer(general_names)

.. versionadded:: 1.2

The certificate issuer is an extension that is only valid inside
:class:`~cryptography.x509.RevokedCertificate` objects. If the
``indirectCRL`` property of the parent CRL's IssuingDistributionPoint
extension is set, then this extension identifies the certificate issuer
associated with the revoked certificate. The object is iterable to get
every element.

:param list general_names: A list of :class:`GeneralName` instances.

.. attribute:: oid

:type: :class:`ObjectIdentifier`

Returns
:attr:`~cryptography.x509.oid.CRLEntryExtensionOID.CERTIFICATE_ISSUER`.

.. method:: get_values_for_type(type)

:param type: A :class:`GeneralName` instance. This is one of the
:ref:`general name classes <general_name_classes>`.

:returns: A list of values extracted from the matched general names.
The type of the returned values depends on the :class:`GeneralName`.


Object Identifiers
~~~~~~~~~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion src/cryptography/hazmat/backends/openssl/x509.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@ def _decode_cert_issuer(backend, ext):
CRLEntryExtensionOID.CERTIFICATE_ISSUER))

gns = backend._ffi.gc(gns, backend._lib.GENERAL_NAMES_free)
return x509.GeneralNames(_decode_general_names(backend, gns))
return x509.CertificateIssuer(_decode_general_names(backend, gns))


@utils.register_interface(x509.RevokedCertificate)
Expand Down
8 changes: 5 additions & 3 deletions src/cryptography/x509/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
from cryptography.x509.extensions import (
AccessDescription, AuthorityInformationAccess,
AuthorityKeyIdentifier, BasicConstraints, CRLDistributionPoints,
CRLNumber, CertificatePolicies, DistributionPoint, DuplicateExtension,
ExtendedKeyUsage, Extension, ExtensionNotFound, ExtensionType, Extensions,
GeneralNames, InhibitAnyPolicy, IssuerAlternativeName, KeyUsage,
CRLNumber, CertificateIssuer, CertificatePolicies,
DistributionPoint, DuplicateExtension, ExtendedKeyUsage, Extension,
ExtensionNotFound, ExtensionType, Extensions, GeneralNames,
InhibitAnyPolicy, IssuerAlternativeName, KeyUsage,
NameConstraints, NoticeReference, OCSPNoCheck, PolicyInformation,
ReasonFlags, SubjectAlternativeName, SubjectKeyIdentifier,
UnsupportedExtension, UserNotice
Expand Down Expand Up @@ -165,4 +166,5 @@
"OID_OCSP",
"_GENERAL_NAMES",
"CRLExtensionOID",
"CertificateIssuer",
]
33 changes: 32 additions & 1 deletion src/cryptography/x509/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
from cryptography.hazmat.primitives import constant_time, serialization
from cryptography.x509.general_name import GeneralName, IPAddress, OtherName
from cryptography.x509.name import Name
from cryptography.x509.oid import ExtensionOID, ObjectIdentifier
from cryptography.x509.oid import (
CRLEntryExtensionOID, ExtensionOID, ObjectIdentifier
)


class _SubjectPublicKeyInfo(univ.Sequence):
Expand Down Expand Up @@ -945,3 +947,32 @@ def __eq__(self, other):

def __ne__(self, other):
return not self == other


@utils.register_interface(ExtensionType)
class CertificateIssuer(object):
oid = CRLEntryExtensionOID.CERTIFICATE_ISSUER

def __init__(self, general_names):
self._general_names = GeneralNames(general_names)

def __iter__(self):
return iter(self._general_names)

def __len__(self):
return len(self._general_names)

def get_values_for_type(self, type):
return self._general_names.get_values_for_type(type)

def __repr__(self):
return "<CertificateIssuer({0})>".format(self._general_names)

def __eq__(self, other):
if not isinstance(other, CertificateIssuer):
return NotImplemented

return self._general_names == other._general_names

def __ne__(self, other):
return not self == other
13 changes: 6 additions & 7 deletions tests/test_x509.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,12 +355,12 @@ def test_revoked_extensions(self, backend):
backend
)

exp_issuer = x509.GeneralNames([
exp_issuer = [
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.
Expand All @@ -383,14 +383,13 @@ def test_revoked_extensions(self, backend):
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
issuer = rev1.extensions.get_extension_for_class(
x509.CertificateIssuer).value
assert issuer == x509.CertificateIssuer(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"
assert date == datetime.datetime(2015, 1, 1, 0, 0)

# Check if all reason flags can be found in the CRL.
flags = set(x509.ReasonFlags)
Expand Down
38 changes: 38 additions & 0 deletions tests/test_x509_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,44 @@ def test_ne(self):
assert ext1 != object()


class TestCertificateIssuer(object):
def test_iter_names(self):
ci = x509.CertificateIssuer([
x509.DNSName(u"cryptography.io"),
x509.DNSName(u"crypto.local"),
])
assert len(ci) == 2
assert list(ci) == [
x509.DNSName(u"cryptography.io"),
x509.DNSName(u"crypto.local"),
]

def test_eq(self):
ci1 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")])
ci2 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")])
assert ci1 == ci2

def test_ne(self):
ci1 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")])
ci2 = x509.CertificateIssuer([x509.DNSName(u"somethingelse.tld")])
assert ci1 != ci2
assert ci1 != object()

def test_repr(self):
ci = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")])
assert repr(ci) == (
"<CertificateIssuer(<GeneralNames([<DNSName(value=cryptography.io"
")>])>)>"
)

def test_get_values_for_type(self):
ci = x509.CertificateIssuer(
[x509.DNSName(u"cryptography.io")]
)
names = ci.get_values_for_type(x509.DNSName)
assert names == [u"cryptography.io"]


class TestNoticeReference(object):
def test_notice_numbers_not_all_int(self):
with pytest.raises(TypeError):
Expand Down

0 comments on commit 0860ef6

Please sign in to comment.