diff options
author | Alex Gaynor <alex.gaynor@gmail.com> | 2015-05-10 12:58:35 -0400 |
---|---|---|
committer | Alex Gaynor <alex.gaynor@gmail.com> | 2015-05-10 12:58:35 -0400 |
commit | b76fee88e248b89beef7d8a80e2700512f88978d (patch) | |
tree | 7dfa3b44615787694fb98008acb51983e1ef10f3 | |
parent | 64e82dd534d04da3d028e264ae2ed17853c69fb3 (diff) | |
parent | 749da3b67c76ca7172eef88b2624b46cb99510a9 (diff) | |
download | cryptography-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.rst | 94 | ||||
-rw-r--r-- | src/cryptography/x509.py | 120 | ||||
-rw-r--r-- | tests/test_x509_ext.py | 292 |
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() |