diff options
-rw-r--r-- | docs/spelling_wordlist.txt | 1 | ||||
-rw-r--r-- | docs/x509.rst | 36 | ||||
-rw-r--r-- | src/cryptography/x509.py | 67 | ||||
-rw-r--r-- | tests/test_x509_ext.py | 142 |
4 files changed, 246 insertions, 0 deletions
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 "<AuthorityInformationAccess({0})>".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 ( + "<AccessDescription(access_method={0.access_method}, access_locati" + "on={0.access_location})>".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) == ( + "<AccessDescription(access_method=<ObjectIdentifier(oid=1.3.6.1.5." + "5.7.48.1, name=OCSP)>, access_location=<UniformResourceIdentifier" + "(value=http://ocsp.domain.com)>)>" + ) + + 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) == ( + "<AuthorityInformationAccess([<AccessDescription(access_method=<Ob" + "jectIdentifier(oid=1.3.6.1.5.5.7.48.1, name=OCSP)>, access_locati" + "on=<UniformResourceIdentifier(value=http://ocsp.domain.com)>)>, <" + "AccessDescription(access_method=<ObjectIdentifier(oid=1.3.6.1.5.5" + ".7.48.2, name=caIssuers)>, access_location=<UniformResourceIdenti" + "fier(value=http://domain.com/ca.crt)>)>])>" + ) + + 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() |