aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/spelling_wordlist.txt1
-rw-r--r--docs/x509.rst43
-rw-r--r--src/cryptography/x509.py69
-rw-r--r--tests/test_x509_ext.py142
4 files changed, 255 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..42468626 100644
--- a/docs/x509.rst
+++ b/docs/x509.rst
@@ -719,6 +719,36 @@ 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`
+
+ 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
~~~~~~~~~~~~~~~~~~
@@ -911,6 +941,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..27337092 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.1": "OCSP",
+ "1.3.6.1.5.5.7.48.2": "caIssuers",
}
@@ -394,6 +396,70 @@ 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 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")
+
+ 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 +746,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..0e5cab50 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(ValueError):
+ 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()