From 3e6d558d1b845cf2df31efec08235b15998174d4 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 2 May 2015 21:57:56 -0500 Subject: add authority information access classes --- docs/spelling_wordlist.txt | 1 + docs/x509.rst | 36 ++++++++++++ src/cryptography/x509.py | 67 +++++++++++++++++++++ tests/test_x509_ext.py | 142 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 246 insertions(+) diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index b7c4c6c2..badb500c 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -40,6 +40,7 @@ multi naïve namespace namespaces +online paddings pickleable plaintext diff --git a/docs/x509.rst b/docs/x509.rst index 5f36a921..f66178ab 100644 --- a/docs/x509.rst +++ b/docs/x509.rst @@ -719,6 +719,29 @@ X.509 Extensions :returns: A list of values extracted from the matched general names. +.. class:: AuthorityInformationAccess + + .. versionadded:: 0.9 + + The authority information access extension indicates how to access + information and services for the issuer of the certificate in which + the extension appears. Information and services may include online + validation services (such as OCSP) and issuer data. It is an iterable, + containing one or more :class:`AccessDescription` instances. + + +.. class:: AccessDescription + + .. attribute:: access_method + + :type: :class:`ObjectIdentifier` + + Either :data:`OID_OCSP` or :data:`OID_CA_ISSUERS` + + .. attribute:: access_location + + :type: :class:`GeneralName` + Object Identifiers ~~~~~~~~~~~~~~~~~~ @@ -911,6 +934,19 @@ Extended Key Usage OIDs Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.9"``. This is used to denote that a certificate may be used for signing OCSP responses. +Authority Information Access OIDs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. data:: OID_OCSP + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.1"``. Used as the + identifier for OCSP data in :class:`AccessDescription` objects. + +.. data:: OID_CA_ISSUERS + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.2"``. Used as the + identifier for CA issuer data in :class:`AccessDescription` objects. + .. _extension_oids: Extension OIDs diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index a37e2d08..2bbd14d7 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -67,6 +67,8 @@ _OID_NAMES = { "1.3.6.1.5.5.7.1.1": "authorityInfoAccess", "1.3.6.1.5.5.7.1.11": "subjectInfoAccess", "1.3.6.1.5.5.7.48.1.5": "OCSPNoCheck", + "1.3.6.1.5.5.7.48.2": "caIssuers", + "1.3.6.1.5.5.7.48.1": "OCSP", } @@ -394,6 +396,68 @@ class KeyUsage(object): self, encipher_only, decipher_only) +class AuthorityInformationAccess(object): + def __init__(self, descriptions): + if not all(isinstance(x, AccessDescription) for x in descriptions): + raise TypeError( + "Every item in the descriptions list must be an " + "AccessDescription" + ) + + self._descriptions = descriptions + + def __iter__(self): + return iter(self._descriptions) + + def __len__(self): + return len(self._descriptions) + + def __repr__(self): + return "".format(self._descriptions) + + def __eq__(self, other): + if not isinstance(other, AuthorityInformationAccess): + return NotImplemented + + return self._descriptions == other._descriptions + + def __ne__(self, other): + return not self == other + + +class AccessDescription(object): + def __init__(self, access_method, access_location): + if not (access_method == OID_OCSP or access_method == OID_CA_ISSUERS): + raise TypeError("access_method must be OID_OCSP or OID_CA_ISSUERS") + + if not isinstance(access_location, GeneralName): + raise TypeError("access_location must be a GeneralName") + + self._access_method = access_method + self._access_location = access_location + + def __repr__(self): + return ( + "".format(self) + ) + + def __eq__(self, other): + if not isinstance(other, AccessDescription): + return NotImplemented + + return ( + self.access_method == other.access_method and + self.access_location == other.access_location + ) + + def __ne__(self, other): + return not self == other + + access_method = utils.read_only_property("_access_method") + access_location = utils.read_only_property("_access_location") + + class SubjectKeyIdentifier(object): def __init__(self, digest): self._digest = digest @@ -680,6 +744,9 @@ OID_EMAIL_PROTECTION = ObjectIdentifier("1.3.6.1.5.5.7.3.4") OID_TIME_STAMPING = ObjectIdentifier("1.3.6.1.5.5.7.3.8") OID_OCSP_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.9") +OID_CA_ISSUERS = ObjectIdentifier("1.3.6.1.5.5.7.48.2") +OID_OCSP = ObjectIdentifier("1.3.6.1.5.5.7.48.1") + @six.add_metaclass(abc.ABCMeta) class Certificate(object): diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py index 92e616e1..711b6b7e 100644 --- a/tests/test_x509_ext.py +++ b/tests/test_x509_ext.py @@ -988,3 +988,145 @@ class TestExtendedKeyUsageExtension(object): x509.ObjectIdentifier("2.5.29.37.0"), x509.ObjectIdentifier("2.16.840.1.113730.4.1"), ] == list(ext.value) + + +class TestAccessDescription(object): + def test_invalid_access_method(self): + with pytest.raises(TypeError): + x509.AccessDescription("notanoid", x509.DNSName(u"test")) + + def test_invalid_access_location(self): + with pytest.raises(TypeError): + x509.AccessDescription(x509.OID_CA_ISSUERS, "invalid") + + def test_repr(self): + ad = x509.AccessDescription( + x509.OID_OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + assert repr(ad) == ( + ", access_location=)>" + ) + + def test_eq(self): + ad = x509.AccessDescription( + x509.OID_OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ad2 = x509.AccessDescription( + x509.OID_OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + assert ad == ad2 + + def test_ne(self): + ad = x509.AccessDescription( + x509.OID_OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ad2 = x509.AccessDescription( + x509.OID_CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ad3 = x509.AccessDescription( + x509.OID_OCSP, + x509.UniformResourceIdentifier(u"http://notthesame") + ) + assert ad != ad2 + assert ad != ad3 + assert ad != object() + + +class TestAuthorityInformationAccess(object): + def test_invalid_descriptions(self): + with pytest.raises(TypeError): + x509.AuthorityInformationAccess(["notanAccessDescription"]) + + def test_iter_len(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + x509.OID_OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + x509.OID_CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + assert len(aia) == 2 + assert list(aia) == [ + x509.AccessDescription( + x509.OID_OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + x509.OID_CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ] + + def test_repr(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + x509.OID_OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + x509.OID_CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + assert repr(aia) == ( + ", access_locati" + "on=)>, <" + "AccessDescription(access_method=, access_location=)>])>" + ) + + def test_eq(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + x509.OID_OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + x509.OID_CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + aia2 = x509.AuthorityInformationAccess([ + x509.AccessDescription( + x509.OID_OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + x509.OID_CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + assert aia == aia2 + + def test_ne(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + x509.OID_OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + x509.OID_CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + aia2 = x509.AuthorityInformationAccess([ + x509.AccessDescription( + x509.OID_OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + ]) + + assert aia != aia2 + assert aia != object() -- cgit v1.2.3 From f506bca3d2bb449c3889cbbaba11749304e81563 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 2 May 2015 22:31:47 -0500 Subject: updates based on review feedback --- docs/x509.rst | 9 ++++++++- src/cryptography/x509.py | 6 ++++-- tests/test_x509_ext.py | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/x509.rst b/docs/x509.rst index f66178ab..42468626 100644 --- a/docs/x509.rst +++ b/docs/x509.rst @@ -736,12 +736,19 @@ X.509 Extensions :type: :class:`ObjectIdentifier` - Either :data:`OID_OCSP` or :data:`OID_CA_ISSUERS` + The access method defines what the ``access_location`` means. It must + be either :data:`OID_OCSP` or :data:`OID_CA_ISSUERS`. If it is + :data:`OID_OCSP` the access location will be where to obtain OCSP + information for the certificate. If it is :data:`OID_CA_ISSUERS` the + access location will provide additional information about the issuing + certificate. .. attribute:: access_location :type: :class:`GeneralName` + Where to access the information defined by the access method. + Object Identifiers ~~~~~~~~~~~~~~~~~~ diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 2bbd14d7..27337092 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -67,8 +67,8 @@ _OID_NAMES = { "1.3.6.1.5.5.7.1.1": "authorityInfoAccess", "1.3.6.1.5.5.7.1.11": "subjectInfoAccess", "1.3.6.1.5.5.7.48.1.5": "OCSPNoCheck", - "1.3.6.1.5.5.7.48.2": "caIssuers", "1.3.6.1.5.5.7.48.1": "OCSP", + "1.3.6.1.5.5.7.48.2": "caIssuers", } @@ -428,7 +428,9 @@ class AuthorityInformationAccess(object): class AccessDescription(object): def __init__(self, access_method, access_location): if not (access_method == OID_OCSP or access_method == OID_CA_ISSUERS): - raise TypeError("access_method must be OID_OCSP or OID_CA_ISSUERS") + raise ValueError( + "access_method must be OID_OCSP or OID_CA_ISSUERS" + ) if not isinstance(access_location, GeneralName): raise TypeError("access_location must be a GeneralName") diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py index 711b6b7e..0e5cab50 100644 --- a/tests/test_x509_ext.py +++ b/tests/test_x509_ext.py @@ -992,7 +992,7 @@ class TestExtendedKeyUsageExtension(object): class TestAccessDescription(object): def test_invalid_access_method(self): - with pytest.raises(TypeError): + with pytest.raises(ValueError): x509.AccessDescription("notanoid", x509.DNSName(u"test")) def test_invalid_access_location(self): -- cgit v1.2.3