Merge pull request #2737 from alex/policy-constraints-bindings

Added policy constraint struct binding
diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst
index ad945f2..d62fb93 100644
--- a/docs/development/test-vectors.rst
+++ b/docs/development/test-vectors.rst
@@ -110,6 +110,9 @@
   containing a SAN extension with an ``ediPartyName`` general name.
 * ``san_x400address.der`` - A DSA certificate from a `Mozilla bug`_ containing
   a SAN extension with an ``x400Address`` general name.
+* ``department-of-state-root.pem`` - The intermediary CA for the Department of
+  State, issued by the United States Federal Government's Common Policy CA.
+  Notably has a ``critical`` policy constraints extensions.
 
 Custom X.509 Vectors
 ~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst
index 8bb3f40..529578b 100644
--- a/docs/x509/reference.rst
+++ b/docs/x509/reference.rst
@@ -1860,6 +1860,44 @@
 
         :type: int
 
+.. class:: PolicyConstraints
+
+    .. versionadded:: 1.3
+
+    The policy constraints extension is used to inhibit policy mapping or
+    require that each certificate in a chain contain an acceptable policy
+    identifier. For more information about the use of this extension see
+    :rfc:`5280`.
+
+    .. attribute:: oid
+
+        :type: :class:`ObjectIdentifier`
+
+        Returns :attr:`~cryptography.x509.oid.ExtensionOID.POLICY_CONSTRAINTS`.
+
+    .. attribute:: require_explicit_policy
+
+        :type: int or None
+
+        If this field is not None, the value indicates the number of additional
+        certificates that may appear in the chain before an explicit policy is
+        required for the entire path. When an explicit policy is required, it
+        is necessary for all certificates in the chain to contain an acceptable
+        policy identifier in the certificate policies extension.  An
+        acceptable policy identifier is the identifier of a policy required
+        by the user of the certification path or the identifier of a policy
+        that has been declared equivalent through policy mapping.
+
+    .. attribute:: inhibit_policy_mapping
+
+        :type: int or None
+
+        If this field is not None, the value indicates the number of additional
+        certificates that may appear in the chain before policy mapping is no
+        longer permitted.  For example, a value of one indicates that policy
+        mapping may be processed in certificates issued by the subject of this
+        certificate, but not in additional certificates in the chain.
+
 .. class:: CRLNumber(crl_number)
 
     .. versionadded:: 1.2
@@ -2392,6 +2430,12 @@
         the ``CRLNumber`` extension type. This extension only has meaning
         for certificate revocation lists.
 
+    .. attribute:: POLICY_CONSTRAINTS
+
+        Corresponds to the dotted string ``"2.5.29.36"``. The identifier for the
+        :class:`~cryptography.x509.PolicyConstraints` extension type.
+
+
 .. class:: CRLEntryExtensionOID
 
     .. versionadded:: 1.2
diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py
index 787f1a6..8d7bad2 100644
--- a/src/cryptography/x509/__init__.py
+++ b/src/cryptography/x509/__init__.py
@@ -20,9 +20,10 @@
     DistributionPoint, DuplicateExtension, ExtendedKeyUsage, Extension,
     ExtensionNotFound, ExtensionType, Extensions, GeneralNames,
     InhibitAnyPolicy, InvalidityDate, IssuerAlternativeName, KeyUsage,
-    NameConstraints, NoticeReference, OCSPNoCheck, PolicyInformation,
-    ReasonFlags, SubjectAlternativeName, SubjectKeyIdentifier,
-    UnrecognizedExtension, UnsupportedExtension, UserNotice
+    NameConstraints, NoticeReference, OCSPNoCheck, PolicyConstraints,
+    PolicyInformation, ReasonFlags, SubjectAlternativeName,
+    SubjectKeyIdentifier, UnrecognizedExtension, UnsupportedExtension,
+    UserNotice
 )
 from cryptography.x509.general_name import (
     DNSName, DirectoryName, GeneralName, IPAddress, OtherName, RFC822Name,
@@ -178,4 +179,5 @@
     "CRLReason",
     "InvalidityDate",
     "UnrecognizedExtension",
+    "PolicyConstraints",
 ]
diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py
index db55789..0aa6721 100644
--- a/src/cryptography/x509/extensions.py
+++ b/src/cryptography/x509/extensions.py
@@ -490,6 +490,62 @@
 
 
 @utils.register_interface(ExtensionType)
+class PolicyConstraints(object):
+    oid = ExtensionOID.POLICY_CONSTRAINTS
+
+    def __init__(self, require_explicit_policy, inhibit_policy_mapping):
+        if require_explicit_policy is not None and not isinstance(
+            require_explicit_policy, six.integer_types
+        ):
+            raise TypeError(
+                "require_explicit_policy must be a non-negative integer or "
+                "None"
+            )
+
+        if inhibit_policy_mapping is not None and not isinstance(
+            inhibit_policy_mapping, six.integer_types
+        ):
+            raise TypeError(
+                "inhibit_policy_mapping must be a non-negative integer or None"
+            )
+
+        if inhibit_policy_mapping is None and require_explicit_policy is None:
+            raise ValueError(
+                "At least one of require_explicit_policy and "
+                "inhibit_policy_mapping must not be None"
+            )
+
+        self._require_explicit_policy = require_explicit_policy
+        self._inhibit_policy_mapping = inhibit_policy_mapping
+
+    def __repr__(self):
+        return (
+            u"<PolicyConstraints(require_explicit_policy={0.require_explicit"
+            u"_policy}, inhibit_policy_mapping={0.inhibit_policy_"
+            u"mapping})>".format(self)
+        )
+
+    def __eq__(self, other):
+        if not isinstance(other, PolicyConstraints):
+            return NotImplemented
+
+        return (
+            self.require_explicit_policy == other.require_explicit_policy and
+            self.inhibit_policy_mapping == other.inhibit_policy_mapping
+        )
+
+    def __ne__(self, other):
+        return not self == other
+
+    require_explicit_policy = utils.read_only_property(
+        "_require_explicit_policy"
+    )
+    inhibit_policy_mapping = utils.read_only_property(
+        "_inhibit_policy_mapping"
+    )
+
+
+@utils.register_interface(ExtensionType)
 class CertificatePolicies(object):
     oid = ExtensionOID.CERTIFICATE_POLICIES
 
diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py
index d8a5f9d..ceb11df 100644
--- a/tests/test_x509_ext.py
+++ b/tests/test_x509_ext.py
@@ -2245,6 +2245,41 @@
         assert hash(ad) != hash(ad3)
 
 
+class TestPolicyConstraints(object):
+    def test_invalid_explicit_policy(self):
+        with pytest.raises(TypeError):
+            x509.PolicyConstraints("invalid", None)
+
+    def test_invalid_inhibit_policy(self):
+        with pytest.raises(TypeError):
+            x509.PolicyConstraints(None, "invalid")
+
+    def test_both_none(self):
+        with pytest.raises(ValueError):
+            x509.PolicyConstraints(None, None)
+
+    def test_repr(self):
+        pc = x509.PolicyConstraints(0, None)
+
+        assert repr(pc) == (
+            u"<PolicyConstraints(require_explicit_policy=0, inhibit_policy_ma"
+            u"pping=None)>"
+        )
+
+    def test_eq(self):
+        pc = x509.PolicyConstraints(2, 1)
+        pc2 = x509.PolicyConstraints(2, 1)
+        assert pc == pc2
+
+    def test_ne(self):
+        pc = x509.PolicyConstraints(2, 1)
+        pc2 = x509.PolicyConstraints(2, 2)
+        pc3 = x509.PolicyConstraints(3, 1)
+        assert pc != pc2
+        assert pc != pc3
+        assert pc != object()
+
+
 class TestAuthorityInformationAccess(object):
     def test_invalid_descriptions(self):
         with pytest.raises(TypeError):
diff --git a/vectors/cryptography_vectors/x509/department-of-state-root.pem b/vectors/cryptography_vectors/x509/department-of-state-root.pem
new file mode 100644
index 0000000..6b6885c
--- /dev/null
+++ b/vectors/cryptography_vectors/x509/department-of-state-root.pem
@@ -0,0 +1,40 @@
+-----BEGIN CERTIFICATE-----
+MIIG+DCCBeCgAwIBAgICITgwDQYJKoZIhvcNAQELBQAwWTELMAkGA1UEBhMCVVMx
+GDAWBgNVBAoTD1UuUy4gR292ZXJubWVudDENMAsGA1UECxMERlBLSTEhMB8GA1UE
+AxMYRmVkZXJhbCBDb21tb24gUG9saWN5IENBMB4XDTE0MDgxMTE0MjUwMloXDTE3
+MDgxMTE0MjM1OVowgbExEzARBgoJkiaJk/IsZAEZFgNzYnUxFTATBgoJkiaJk/Is
+ZAEZFgVzdGF0ZTEWMBQGA1UEAxMNQ29uZmlndXJhdGlvbjERMA8GA1UEAxMIU2Vy
+dmljZXMxHDAaBgNVBAMTE1B1YmxpYyBLZXkgU2VydmljZXMxDDAKBgNVBAMTA0FJ
+QTEsMCoGA1UEAxMjVS5TLiBEZXBhcnRtZW50IG9mIFN0YXRlIEFEIFJvb3QgQ0Ew
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5Nl+zsQXaSuJrw5d/SlwQ
+2Qopr9lvmlWvpRlutPSl7X5Dg5WSyU2V0u++JE0uprOhs+ZI9WS27MK+a32OmMzT
+g6HVzpO3curRiM/h5fJrAEBYFeXour0oUHYFwWcChi1k0mZkMrb4WO9+WppTFlIv
+9b7MSgaOoH5UTUNE5HAMMDgPVQGnIsxylftF7ikCyld45N764HDLdlfzw2j0RHn1
+ntEw2q0pp6vGbBN9/RPh97rOqJrKIr3ieE4Bzw3b+gAF4ymjcKOigl2lih8PJG5Z
+puGmPwOmWXh+rmwnzAM6pQmHp/xXDIPqJx5hvTfoOKsJhBiePgwQOc0blYgsXM7n
+AgMBAAGjggNvMIIDazAPBgNVHRMBAf8EBTADAQH/MGsGA1UdIARkMGIwDAYKYIZI
+AWUDAgEDAjAMBgpghkgBZQMCAQMNMAwGCmCGSAFlAwIBAwEwDAYKYIZIAWUDAgED
+BjAMBgpghkgBZQMCAQMHMAwGCmCGSAFlAwIBAwgwDAYKYIZIAWUDAgEDEDBPBggr
+BgEFBQcBAQRDMEEwPwYIKwYBBQUHMAKGM2h0dHA6Ly9odHRwLmZwa2kuZ292L2Zj
+cGNhL2NhQ2VydHNJc3N1ZWRUb2ZjcGNhLnA3YzCBjQYDVR0hBIGFMIGCMBgGCmCG
+SAFlAwIBAwEGCmCGSAFlAwIBBgEwGAYKYIZIAWUDAgEDAgYKYIZIAWUDAgEGAjAY
+BgpghkgBZQMCAQMGBgpghkgBZQMCAQYDMBgGCmCGSAFlAwIBAwcGCmCGSAFlAwIB
+BgwwGAYKYIZIAWUDAgEDEAYKYIZIAWUDAgEGBDCCATYGCCsGAQUFBwELBIIBKDCC
+ASQwQwYIKwYBBQUHMAWGN2h0dHA6Ly9jcmxzLnBraS5zdGF0ZS5nb3YvU0lBL0Nl
+cnRzSXNzdWVkQnlBRFJvb3RDQS5wN2MwgdwGCCsGAQUFBzAFhoHPbGRhcDovL2Nl
+cnRyZXAucGtpLnN0YXRlLmdvdi9jbj1VLlMuJTIwRGVwYXJ0bWVudCUyMG9mJTIw
+U3RhdGUlMjBBRCUyMFJvb3QlMjBDQSxjbj1BSUEsY249UHVibGljJTIwS2V5JTIw
+U2VydmljZXMsY249U2VydmljZXMsY249Q29uZmlndXJhdGlvbixkYz1zdGF0ZSxk
+Yz1zYnU/Y0FDZXJ0aWZpY2F0ZTtiaW5hcnksY3Jvc3NDZXJ0aWZpY2F0ZVBhaXI7
+YmluYXJ5MCkGA1UdHgEB/wQfMB2hGzAZpBcwFTETMBEGCgmSJomT8ixkARkWA21p
+bDAPBgNVHSQBAf8EBTADgQEAMA0GA1UdNgEB/wQDAgEAMA4GA1UdDwEB/wQEAwIB
+BjAfBgNVHSMEGDAWgBStDHp1XOXzmMR5mA6sKP2X9OcC/DA1BgNVHR8ELjAsMCqg
+KKAmhiRodHRwOi8vaHR0cC5mcGtpLmdvdi9mY3BjYS9mY3BjYS5jcmwwHQYDVR0O
+BBYEFG+D/oJQZGV3Pv3fA5rOKdEvMMzsMA0GCSqGSIb3DQEBCwUAA4IBAQBYnF0+
+cCv5Kbqafkn8hdljuUnhCDHKVVL7gysmZUCsIerzzklPWaCrWgTO+yRzIA7oHX4n
+o9sLVpru8evQL5IQVYUCnHIoCWPW9LIvt7eKJHi0KdTlbK5JTN6SNo1MVn1z1L+D
+15nUhuqs3b5FWxCl9AbO0V5tsiAIq6dNrhdfhUJIOLzDffM24HOC3wIDERAfZiPC
+LsK0nOHixW8dNQ8XsT6ZVJ4xhxJ9zW2O2wp/sWJhnzGUYd43IYH0AriHlByYJ3Ef
+vWtu74ypiI9C5xgnzW+rqze4DxG38+QC0V4kIB5adGghRD/qdPElw4hMCzbbZGGs
++OJGofrihzt0GyNM
+-----END CERTIFICATE-----
\ No newline at end of file