aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2015-05-10 12:58:35 -0400
committerAlex Gaynor <alex.gaynor@gmail.com>2015-05-10 12:58:35 -0400
commitb76fee88e248b89beef7d8a80e2700512f88978d (patch)
tree7dfa3b44615787694fb98008acb51983e1ef10f3
parent64e82dd534d04da3d028e264ae2ed17853c69fb3 (diff)
parent749da3b67c76ca7172eef88b2624b46cb99510a9 (diff)
downloadcryptography-b76fee88e248b89beef7d8a80e2700512f88978d.tar.gz
cryptography-b76fee88e248b89beef7d8a80e2700512f88978d.tar.bz2
cryptography-b76fee88e248b89beef7d8a80e2700512f88978d.zip
Merge pull request #1906 from reaperhulk/cdp
add CRLDistributionPoints and associated classes
-rw-r--r--docs/x509.rst94
-rw-r--r--src/cryptography/x509.py120
-rw-r--r--tests/test_x509_ext.py292
3 files changed, 506 insertions, 0 deletions
diff --git a/docs/x509.rst b/docs/x509.rst
index f4ea2a52..86673e3b 100644
--- a/docs/x509.rst
+++ b/docs/x509.rst
@@ -781,6 +781,8 @@ X.509 Extensions
.. class:: AccessDescription
+ .. versionadded:: 0.9
+
.. attribute:: access_method
:type: :class:`ObjectIdentifier`
@@ -798,6 +800,98 @@ X.509 Extensions
Where to access the information defined by the access method.
+.. class:: CRLDistributionPoints
+
+ .. versionadded:: 0.9
+
+ The CRL distribution points extension identifies how CRL information is
+ obtained. It is an iterable, containing one or more
+ :class:`DistributionPoint` instances.
+
+.. class:: DistributionPoint
+
+ .. versionadded:: 0.9
+
+ .. attribute:: full_name
+
+ :type: list of :class:`GeneralName` instances or None
+
+ This field describes methods to retrieve the CRL. At most one of
+ ``full_name`` or ``relative_name`` will be non-None.
+
+ .. attribute:: relative_name
+
+ :type: :class:`Name` or None
+
+ This field describes methods to retrieve the CRL relative to the CRL
+ issuer. At most one of ``full_name`` or ``relative_name`` will be
+ non-None.
+
+ .. attribute:: crl_issuer
+
+ :type: list of :class:`GeneralName` instances or None
+
+ Information about the issuer of the CRL.
+
+ .. attribute:: reasons
+
+ :type: frozenset of :class:`ReasonFlags` or None
+
+ The reasons a given distribution point may be used for when performing
+ revocation checks.
+
+.. class:: ReasonFlags
+
+ .. versionadded:: 0.9
+
+ An enumeration for CRL reasons.
+
+ .. attribute:: unspecified
+
+ It is unspecified why the certificate was revoked. This reason cannot
+ be used as a reason flag in a :class:`DistributionPoint`.
+
+ .. attribute:: key_compromise
+
+ This reason indicates that the private key was compromised.
+
+ .. attribute:: ca_compromise
+
+ This reason indicates that the CA issuing the certificate was
+ compromised.
+
+ .. attribute:: affiliation_changed
+
+ This reason indicates that the subject's name or other information has
+ changed.
+
+ .. attribute:: superseded
+
+ This reason indicates that a certificate has been superseded.
+
+ .. attribute:: cessation_of_operation
+
+ This reason indicates that the certificate is no longer required.
+
+ .. attribute:: certificate_hold
+
+ This reason indicates that the certificate is on hold.
+
+ .. attribute:: privilege_withdrawn
+
+ This reason indicates that the privilege granted by this certificate
+ have been withdrawn.
+
+ .. attribute:: aa_compromise
+
+ When an attribute authority has been compromised.
+
+ .. attribute:: remove_from_crl
+
+ This reason indicates that the certificate was on hold and should be
+ removed from the CRL. This reason cannot be used as a reason flag
+ in a :class:`DistributionPoint`.
+
Object Identifiers
~~~~~~~~~~~~~~~~~~
diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py
index 0d87cd51..dfc0af8c 100644
--- a/src/cryptography/x509.py
+++ b/src/cryptography/x509.py
@@ -481,6 +481,126 @@ class SubjectKeyIdentifier(object):
return not self == other
+class CRLDistributionPoints(object):
+ def __init__(self, distribution_points):
+ if not all(
+ isinstance(x, DistributionPoint) for x in distribution_points
+ ):
+ raise TypeError(
+ "distribution_points must be a list of DistributionPoint "
+ "objects"
+ )
+
+ self._distribution_points = distribution_points
+
+ def __iter__(self):
+ return iter(self._distribution_points)
+
+ def __len__(self):
+ return len(self._distribution_points)
+
+ def __repr__(self):
+ return "<CRLDistributionPoints({0})>".format(self._distribution_points)
+
+ def __eq__(self, other):
+ if not isinstance(other, CRLDistributionPoints):
+ return NotImplemented
+
+ return self._distribution_points == other._distribution_points
+
+ def __ne__(self, other):
+ return not self == other
+
+
+class DistributionPoint(object):
+ def __init__(self, full_name, relative_name, reasons, crl_issuer):
+ if full_name and relative_name:
+ raise ValueError(
+ "At least one of full_name and relative_name must be None"
+ )
+
+ if full_name and not all(
+ isinstance(x, GeneralName) for x in full_name
+ ):
+ raise TypeError(
+ "full_name must be a list of GeneralName objects"
+ )
+
+ if relative_name and not isinstance(relative_name, Name):
+ raise TypeError("relative_name must be a Name")
+
+ if crl_issuer and not all(
+ isinstance(x, GeneralName) for x in crl_issuer
+ ):
+ raise TypeError(
+ "crl_issuer must be None or a list of general names"
+ )
+
+ if reasons and (not isinstance(reasons, frozenset) or not all(
+ isinstance(x, ReasonFlags) for x in reasons
+ )):
+ raise TypeError("reasons must be None or frozenset of ReasonFlags")
+
+ if reasons and (
+ ReasonFlags.unspecified in reasons or
+ ReasonFlags.remove_from_crl in reasons
+ ):
+ raise ValueError(
+ "unspecified and remove_from_crl are not valid reasons in a "
+ "DistributionPoint"
+ )
+
+ if reasons and not crl_issuer and not (full_name or relative_name):
+ raise ValueError(
+ "You must supply crl_issuer, full_name, or relative_name when "
+ "reasons is not None"
+ )
+
+ self._full_name = full_name
+ self._relative_name = relative_name
+ self._reasons = reasons
+ self._crl_issuer = crl_issuer
+
+ def __repr__(self):
+ return (
+ "<DistributionPoint(full_name={0.full_name}, relative_name={0.rela"
+ "tive_name}, reasons={0.reasons}, crl_issuer={0.crl_is"
+ "suer})>".format(self)
+ )
+
+ def __eq__(self, other):
+ if not isinstance(other, DistributionPoint):
+ return NotImplemented
+
+ return (
+ self.full_name == other.full_name and
+ self.relative_name == other.relative_name and
+ self.reasons == other.reasons and
+ self.crl_issuer == other.crl_issuer
+ )
+
+ def __ne__(self, other):
+ return not self == other
+
+ full_name = utils.read_only_property("_full_name")
+ relative_name = utils.read_only_property("_relative_name")
+ reasons = utils.read_only_property("_reasons")
+ crl_issuer = utils.read_only_property("_crl_issuer")
+
+
+class ReasonFlags(Enum):
+ unspecified = "unspecified"
+ key_compromise = "keyCompromise"
+ ca_compromise = "cACompromise"
+ affiliation_changed = "affiliationChanged"
+ superseded = "superseded"
+ cessation_of_operation = "cessationOfOperation"
+ certificate_hold = "certificateHold"
+ privilege_withdrawn = "privilegeWithdrawn"
+ aa_compromise = "aACompromise"
+ remove_from_crl = "removeFromCRL"
+
+
@six.add_metaclass(abc.ABCMeta)
class GeneralName(object):
@abc.abstractproperty
diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py
index 8a227953..06a68600 100644
--- a/tests/test_x509_ext.py
+++ b/tests/test_x509_ext.py
@@ -1318,3 +1318,295 @@ class TestAuthorityKeyIdentifierExtension(object):
)
]
assert ext.value.authority_cert_serial_number == 3
+
+
+class TestDistributionPoint(object):
+ def test_distribution_point_full_name_not_general_names(self):
+ with pytest.raises(TypeError):
+ x509.DistributionPoint(["notgn"], None, None, None)
+
+ def test_distribution_point_relative_name_not_name(self):
+ with pytest.raises(TypeError):
+ x509.DistributionPoint(None, "notname", None, None)
+
+ def test_distribution_point_full_and_relative_not_none(self):
+ with pytest.raises(ValueError):
+ x509.DistributionPoint("data", "notname", None, None)
+
+ def test_crl_issuer_not_general_names(self):
+ with pytest.raises(TypeError):
+ x509.DistributionPoint(None, None, None, ["notgn"])
+
+ def test_reason_not_reasonflags(self):
+ with pytest.raises(TypeError):
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"http://crypt.og/crl")],
+ None,
+ frozenset(["notreasonflags"]),
+ None
+ )
+
+ def test_reason_not_frozenset(self):
+ with pytest.raises(TypeError):
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"http://crypt.og/crl")],
+ None,
+ [x509.ReasonFlags.ca_compromise],
+ None
+ )
+
+ def test_disallowed_reasons(self):
+ with pytest.raises(ValueError):
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"http://crypt.og/crl")],
+ None,
+ frozenset([x509.ReasonFlags.unspecified]),
+ None
+ )
+
+ with pytest.raises(ValueError):
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"http://crypt.og/crl")],
+ None,
+ frozenset([x509.ReasonFlags.remove_from_crl]),
+ None
+ )
+
+ def test_reason_only(self):
+ with pytest.raises(ValueError):
+ x509.DistributionPoint(
+ None,
+ None,
+ frozenset([x509.ReasonFlags.aa_compromise]),
+ None
+ )
+
+ def test_eq(self):
+ dp = x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"http://crypt.og/crl")],
+ None,
+ frozenset([x509.ReasonFlags.superseded]),
+ [
+ x509.DirectoryName(
+ x509.Name([
+ x509.NameAttribute(
+ x509.OID_COMMON_NAME, "Important CA"
+ )
+ ])
+ )
+ ],
+ )
+ dp2 = x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"http://crypt.og/crl")],
+ None,
+ frozenset([x509.ReasonFlags.superseded]),
+ [
+ x509.DirectoryName(
+ x509.Name([
+ x509.NameAttribute(
+ x509.OID_COMMON_NAME, "Important CA"
+ )
+ ])
+ )
+ ],
+ )
+ assert dp == dp2
+
+ def test_ne(self):
+ dp = x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"http://crypt.og/crl")],
+ None,
+ frozenset([x509.ReasonFlags.superseded]),
+ [
+ x509.DirectoryName(
+ x509.Name([
+ x509.NameAttribute(
+ x509.OID_COMMON_NAME, "Important CA"
+ )
+ ])
+ )
+ ],
+ )
+ dp2 = x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"http://crypt.og/crl")],
+ None,
+ None,
+ None
+ )
+ assert dp != dp2
+ assert dp != object()
+
+ def test_repr(self):
+ dp = x509.DistributionPoint(
+ None,
+ x509.Name([
+ x509.NameAttribute(x509.OID_COMMON_NAME, "myCN")
+ ]),
+ frozenset([x509.ReasonFlags.ca_compromise]),
+ [
+ x509.DirectoryName(
+ x509.Name([
+ x509.NameAttribute(
+ x509.OID_COMMON_NAME, "Important CA"
+ )
+ ])
+ )
+ ],
+ )
+ if six.PY3:
+ assert repr(dp) == (
+ "<DistributionPoint(full_name=None, relative_name=<Name([<Name"
+ "Attribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)"
+ ">, value='myCN')>])>, reasons=frozenset({<ReasonFlags.ca_comp"
+ "romise: 'cACompromise'>}), crl_issuer=[<DirectoryName(value=<"
+ "Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name="
+ "commonName)>, value='Important CA')>])>)>])>"
+ )
+ else:
+ assert repr(dp) == (
+ "<DistributionPoint(full_name=None, relative_name=<Name([<Name"
+ "Attribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)"
+ ">, value='myCN')>])>, reasons=frozenset([<ReasonFlags.ca_comp"
+ "romise: 'cACompromise'>]), crl_issuer=[<DirectoryName(value=<"
+ "Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name="
+ "commonName)>, value='Important CA')>])>)>])>"
+ )
+
+
+class TestCRLDistributionPoints(object):
+ def test_invalid_distribution_points(self):
+ with pytest.raises(TypeError):
+ x509.CRLDistributionPoints(["notadistributionpoint"])
+
+ def test_iter_len(self):
+ cdp = x509.CRLDistributionPoints([
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"http://domain")],
+ None,
+ None,
+ None
+ ),
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"ftp://domain")],
+ None,
+ frozenset([
+ x509.ReasonFlags.key_compromise,
+ x509.ReasonFlags.ca_compromise,
+ ]),
+ None
+ ),
+ ])
+ assert len(cdp) == 2
+ assert list(cdp) == [
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"http://domain")],
+ None,
+ None,
+ None
+ ),
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"ftp://domain")],
+ None,
+ frozenset([
+ x509.ReasonFlags.key_compromise,
+ x509.ReasonFlags.ca_compromise,
+ ]),
+ None
+ ),
+ ]
+
+ def test_repr(self):
+ cdp = x509.CRLDistributionPoints([
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"ftp://domain")],
+ None,
+ frozenset([x509.ReasonFlags.key_compromise]),
+ None
+ ),
+ ])
+ if six.PY3:
+ assert repr(cdp) == (
+ "<CRLDistributionPoints([<DistributionPoint(full_name=[<Unifo"
+ "rmResourceIdentifier(value=ftp://domain)>], relative_name=No"
+ "ne, reasons=frozenset({<ReasonFlags.key_compromise: 'keyComp"
+ "romise'>}), crl_issuer=None)>])>"
+ )
+ else:
+ assert repr(cdp) == (
+ "<CRLDistributionPoints([<DistributionPoint(full_name=[<Unifo"
+ "rmResourceIdentifier(value=ftp://domain)>], relative_name=No"
+ "ne, reasons=frozenset([<ReasonFlags.key_compromise: 'keyComp"
+ "romise'>]), crl_issuer=None)>])>"
+ )
+
+ def test_eq(self):
+ cdp = x509.CRLDistributionPoints([
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"ftp://domain")],
+ None,
+ frozenset([
+ x509.ReasonFlags.key_compromise,
+ x509.ReasonFlags.ca_compromise,
+ ]),
+ [x509.UniformResourceIdentifier(u"uri://thing")],
+ ),
+ ])
+ cdp2 = x509.CRLDistributionPoints([
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"ftp://domain")],
+ None,
+ frozenset([
+ x509.ReasonFlags.key_compromise,
+ x509.ReasonFlags.ca_compromise,
+ ]),
+ [x509.UniformResourceIdentifier(u"uri://thing")],
+ ),
+ ])
+ assert cdp == cdp2
+
+ def test_ne(self):
+ cdp = x509.CRLDistributionPoints([
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"ftp://domain")],
+ None,
+ frozenset([
+ x509.ReasonFlags.key_compromise,
+ x509.ReasonFlags.ca_compromise,
+ ]),
+ [x509.UniformResourceIdentifier(u"uri://thing")],
+ ),
+ ])
+ cdp2 = x509.CRLDistributionPoints([
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"ftp://domain2")],
+ None,
+ frozenset([
+ x509.ReasonFlags.key_compromise,
+ x509.ReasonFlags.ca_compromise,
+ ]),
+ [x509.UniformResourceIdentifier(u"uri://thing")],
+ ),
+ ])
+ cdp3 = x509.CRLDistributionPoints([
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"ftp://domain")],
+ None,
+ frozenset([x509.ReasonFlags.key_compromise]),
+ [x509.UniformResourceIdentifier(u"uri://thing")],
+ ),
+ ])
+ cdp4 = x509.CRLDistributionPoints([
+ x509.DistributionPoint(
+ [x509.UniformResourceIdentifier(u"ftp://domain")],
+ None,
+ frozenset([
+ x509.ReasonFlags.key_compromise,
+ x509.ReasonFlags.ca_compromise,
+ ]),
+ [x509.UniformResourceIdentifier(u"uri://thing2")],
+ ),
+ ])
+ assert cdp != cdp2
+ assert cdp != cdp3
+ assert cdp != cdp4
+ assert cdp != object()