diff options
author | Alex Gaynor <alex.gaynor@gmail.com> | 2015-05-11 23:17:52 -0400 |
---|---|---|
committer | Alex Gaynor <alex.gaynor@gmail.com> | 2015-05-11 23:17:52 -0400 |
commit | c6f6a247bf281d4307bcb76be873a743660d0438 (patch) | |
tree | ef93be53ba4e107b9b8f62fb100144d4270c355c | |
parent | 6bfaab4a3e98a8704b4856166debdbae8e9e5915 (diff) | |
parent | 73be2ca86049fd15f1ab37d7201a9b32264402ab (diff) | |
download | cryptography-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.rst | 86 | ||||
-rw-r--r-- | src/cryptography/x509.py | 97 | ||||
-rw-r--r-- | tests/test_x509_ext.py | 119 |
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): |