aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2015-05-11 23:17:52 -0400
committerAlex Gaynor <alex.gaynor@gmail.com>2015-05-11 23:17:52 -0400
commitc6f6a247bf281d4307bcb76be873a743660d0438 (patch)
treeef93be53ba4e107b9b8f62fb100144d4270c355c
parent6bfaab4a3e98a8704b4856166debdbae8e9e5915 (diff)
parent73be2ca86049fd15f1ab37d7201a9b32264402ab (diff)
downloadcryptography-c6f6a247bf281d4307bcb76be873a743660d0438.tar.gz
cryptography-c6f6a247bf281d4307bcb76be873a743660d0438.tar.bz2
cryptography-c6f6a247bf281d4307bcb76be873a743660d0438.zip
Merge pull request #1853 from reaperhulk/certificate-policies
certificate policies extension support
-rw-r--r--docs/x509.rst86
-rw-r--r--src/cryptography/x509.py97
-rw-r--r--tests/test_x509_ext.py119
3 files changed, 302 insertions, 0 deletions
diff --git a/docs/x509.rst b/docs/x509.rst
index 86673e3b..d2313292 100644
--- a/docs/x509.rst
+++ b/docs/x509.rst
@@ -892,6 +892,81 @@ X.509 Extensions
removed from the CRL. This reason cannot be used as a reason flag
in a :class:`DistributionPoint`.
+.. class:: CertificatePolicies
+
+ .. versionadded:: 0.9
+
+ The certificate policies extension is an iterable, containing one or more
+ :class:`PolicyInformation` instances.
+
+Certificate Policies Classes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+These classes may be present within a :class:`CertificatePolicies` instance.
+
+.. class:: PolicyInformation
+
+ .. versionadded:: 0.9
+
+ Contains a policy identifier and an optional list of qualifiers.
+
+ .. attribute:: policy_identifier
+
+ :type: :class:`ObjectIdentifier`
+
+ .. attribute:: policy_qualifiers
+
+ :type: list
+
+ A list consisting of :term:`text` and/or :class:`UserNotice` objects.
+ If the value is text it is a pointer to the practice statement
+ published by the certificate authority. If it is a user notice it is
+ meant for display to the relying party when the certificate is
+ used.
+
+.. class:: UserNotice
+
+ .. versionadded:: 0.9
+
+ User notices are intended for display to a relying party when a certificate
+ is used. In practice, few if any UIs expose this data and it is a rarely
+ encoded component.
+
+ .. attribute:: notice_reference
+
+ :type: :class:`NoticeReference` or None
+
+ The notice reference field names an organization and identifies,
+ by number, a particular statement prepared by that organization.
+
+ .. attribute:: explicit_text
+
+ This field includes an arbitrary textual statement directly in the
+ certificate.
+
+ :type: :term:`text`
+
+.. class:: NoticeReference
+
+ Notice reference can name an organization and provide information about
+ notices related to the certificate. For example, it might identify the
+ organization name and notice number 1. Application software could
+ have a notice file containing the current set of notices for the named
+ organization; the application would then extract the notice text from the
+ file and display it. In practice this is rarely seen.
+
+ .. versionadded:: 0.9
+
+ .. attribute:: organization
+
+ :type: :term:`text` or None
+
+ .. attribute:: notice_numbers
+
+ :type: list or None
+
+ A list of integers or None.
+
Object Identifiers
~~~~~~~~~~~~~~~~~~
@@ -1097,6 +1172,17 @@ Authority Information Access OIDs
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.
+Policy Qualifier OIDs
+~~~~~~~~~~~~~~~~~~~~~
+
+.. data:: OID_CPS_QUALIFIER
+
+ Corresponds to the dotted string ``"1.3.6.1.5.5.7.2.1"``.
+
+.. data:: OID_CPS_USER_NOTICE
+
+ Corresponds to the dotted string ``"1.3.6.1.5.5.7.2.2"``.
+
.. _extension_oids:
Extension OIDs
diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py
index dfc0af8c..50fae716 100644
--- a/src/cryptography/x509.py
+++ b/src/cryptography/x509.py
@@ -69,6 +69,8 @@ _OID_NAMES = {
"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",
+ "1.3.6.1.5.5.7.2.1": "id-qt-cps",
+ "1.3.6.1.5.5.7.2.2": "id-qt-unotice",
}
@@ -460,6 +462,98 @@ class AccessDescription(object):
access_location = utils.read_only_property("_access_location")
+class CertificatePolicies(object):
+ def __init__(self, policies):
+ if not all(isinstance(x, PolicyInformation) for x in policies):
+ raise TypeError(
+ "Every item in the policies list must be a "
+ "PolicyInformation"
+ )
+
+ self._policies = policies
+
+ def __iter__(self):
+ return iter(self._policies)
+
+ def __len__(self):
+ return len(self._policies)
+
+ def __repr__(self):
+ return "<CertificatePolicies({0})>".format(self._policies)
+
+
+class PolicyInformation(object):
+ def __init__(self, policy_identifier, policy_qualifiers):
+ if not isinstance(policy_identifier, ObjectIdentifier):
+ raise TypeError("policy_identifier must be an ObjectIdentifier")
+
+ self._policy_identifier = policy_identifier
+ if policy_qualifiers and not all(
+ isinstance(
+ x, (six.text_type, UserNotice)
+ ) for x in policy_qualifiers
+ ):
+ raise TypeError(
+ "policy_qualifiers must be a list of strings and/or UserNotice"
+ " objects or None"
+ )
+
+ self._policy_qualifiers = policy_qualifiers
+
+ def __repr__(self):
+ return (
+ "<PolicyInformation(policy_identifier={0.policy_identifier}, polic"
+ "y_qualifiers={0.policy_qualifiers})>".format(self)
+ )
+
+ policy_identifier = utils.read_only_property("_policy_identifier")
+ policy_qualifiers = utils.read_only_property("_policy_qualifiers")
+
+
+class UserNotice(object):
+ def __init__(self, notice_reference, explicit_text):
+ if notice_reference and not isinstance(
+ notice_reference, NoticeReference
+ ):
+ raise TypeError(
+ "notice_reference must be None or a NoticeReference"
+ )
+
+ self._notice_reference = notice_reference
+ self._explicit_text = explicit_text
+
+ def __repr__(self):
+ return (
+ "<UserNotice(notice_reference={0.notice_reference}, explicit_text="
+ "{0.explicit_text!r})>".format(self)
+ )
+
+ notice_reference = utils.read_only_property("_notice_reference")
+ explicit_text = utils.read_only_property("_explicit_text")
+
+
+class NoticeReference(object):
+ def __init__(self, organization, notice_numbers):
+ self._organization = organization
+ if notice_numbers and not all(
+ isinstance(x, int) for x in notice_numbers
+ ):
+ raise TypeError(
+ "notice_numbers must be a list of integers or None"
+ )
+
+ self._notice_numbers = notice_numbers
+
+ def __repr__(self):
+ return (
+ "<NoticeReference(organization={0.organization!r}, notice_numbers="
+ "{0.notice_numbers})>".format(self)
+ )
+
+ organization = utils.read_only_property("_organization")
+ notice_numbers = utils.read_only_property("_notice_numbers")
+
+
class SubjectKeyIdentifier(object):
def __init__(self, digest):
self._digest = digest
@@ -874,6 +968,9 @@ 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")
+OID_CPS_QUALIFIER = ObjectIdentifier("1.3.6.1.5.5.7.2.1")
+OID_CPS_USER_NOTICE = ObjectIdentifier("1.3.6.1.5.5.7.2.2")
+
@six.add_metaclass(abc.ABCMeta)
class Certificate(object):
diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py
index 06a68600..ae69f5fc 100644
--- a/tests/test_x509_ext.py
+++ b/tests/test_x509_ext.py
@@ -39,6 +39,125 @@ class TestExtension(object):
)
+class TestNoticeReference(object):
+ def test_notice_numbers_not_all_int(self):
+ with pytest.raises(TypeError):
+ x509.NoticeReference("org", [1, 2, "three"])
+
+ def test_notice_numbers_none(self):
+ nr = x509.NoticeReference("org", None)
+ assert nr.organization == "org"
+ assert nr.notice_numbers is None
+
+ def test_repr(self):
+ nr = x509.NoticeReference(u"org", [1, 3, 4])
+
+ if six.PY3:
+ assert repr(nr) == (
+ "<NoticeReference(organization='org', notice_numbers=[1, 3, 4"
+ "])>"
+ )
+ else:
+ assert repr(nr) == (
+ "<NoticeReference(organization=u'org', notice_numbers=[1, 3, "
+ "4])>"
+ )
+
+
+class TestUserNotice(object):
+ def test_notice_reference_invalid(self):
+ with pytest.raises(TypeError):
+ x509.UserNotice("invalid", None)
+
+ def test_notice_reference_none(self):
+ un = x509.UserNotice(None, "text")
+ assert un.notice_reference is None
+ assert un.explicit_text == "text"
+
+ def test_repr(self):
+ un = x509.UserNotice(x509.NoticeReference(u"org", None), u"text")
+ if six.PY3:
+ assert repr(un) == (
+ "<UserNotice(notice_reference=<NoticeReference(organization='"
+ "org', notice_numbers=None)>, explicit_text='text')>"
+ )
+ else:
+ assert repr(un) == (
+ "<UserNotice(notice_reference=<NoticeReference(organization=u"
+ "'org', notice_numbers=None)>, explicit_text=u'text')>"
+ )
+
+
+class TestPolicyInformation(object):
+ def test_invalid_policy_identifier(self):
+ with pytest.raises(TypeError):
+ x509.PolicyInformation("notanoid", None)
+
+ def test_none_policy_qualifiers(self):
+ pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), None)
+ assert pi.policy_identifier == x509.ObjectIdentifier("1.2.3")
+ assert pi.policy_qualifiers is None
+
+ def test_policy_qualifiers(self):
+ pq = [u"string"]
+ pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq)
+ assert pi.policy_identifier == x509.ObjectIdentifier("1.2.3")
+ assert pi.policy_qualifiers == pq
+
+ def test_invalid_policy_identifiers(self):
+ with pytest.raises(TypeError):
+ x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), [1, 2])
+
+ def test_repr(self):
+ pq = [u"string", x509.UserNotice(None, u"hi")]
+ pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq)
+ if six.PY3:
+ assert repr(pi) == (
+ "<PolicyInformation(policy_identifier=<ObjectIdentifier(oid=1."
+ "2.3, name=Unknown OID)>, policy_qualifiers=['string', <UserNo"
+ "tice(notice_reference=None, explicit_text='hi')>])>"
+ )
+ else:
+ assert repr(pi) == (
+ "<PolicyInformation(policy_identifier=<ObjectIdentifier(oid=1."
+ "2.3, name=Unknown OID)>, policy_qualifiers=[u'string', <UserN"
+ "otice(notice_reference=None, explicit_text=u'hi')>])>"
+ )
+
+
+class TestCertificatePolicies(object):
+ def test_invalid_policies(self):
+ pq = [u"string"]
+ pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq)
+ with pytest.raises(TypeError):
+ x509.CertificatePolicies([1, pi])
+
+ def test_iter_len(self):
+ pq = [u"string"]
+ pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq)
+ cp = x509.CertificatePolicies([pi])
+ assert len(cp) == 1
+ for policyinfo in cp:
+ assert policyinfo == pi
+
+ def test_repr(self):
+ pq = [u"string"]
+ pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq)
+ cp = x509.CertificatePolicies([pi])
+ if six.PY3:
+ assert repr(cp) == (
+ "<CertificatePolicies([<PolicyInformation(policy_identifier=<O"
+ "bjectIdentifier(oid=1.2.3, name=Unknown OID)>, policy_qualifi"
+ "ers=['string'])>])>"
+ )
+ else:
+ assert repr(cp) == (
+ "<CertificatePolicies([<PolicyInformation(policy_identifier=<O"
+ "bjectIdentifier(oid=1.2.3, name=Unknown OID)>, policy_qualifi"
+ "ers=[u'string'])>])>"
+ )
+
+
class TestKeyUsage(object):
def test_key_agreement_false_encipher_decipher_true(self):
with pytest.raises(ValueError):