add nameconstraints classes
diff --git a/docs/x509.rst b/docs/x509.rst
index ed7b871..1e4efb4 100644
--- a/docs/x509.rst
+++ b/docs/x509.rst
@@ -814,6 +814,32 @@
     extension is only relevant when the certificate is an authorized OCSP
     responder.
 
+.. class:: NameConstraints
+
+    .. versionadded:: 1.0
+
+    The name constraints extension, which only has meaning in a CA certificate,
+    defines a name space within which all subject names in certificates issued
+    beneath the CA certificate must (or must not) be in. For specific details
+    on the way this extension should be processed see :rfc:`5280`.
+
+    .. attribute:: permitted_subtrees
+
+        :type: list of :class:`GeneralName` objects or None
+
+        The set of permitted name patterns. If a name matches this and an
+        element in ``excluded_subtrees`` it is invalid. At least one of
+        ``permitted_subtrees`` and ``excluded_subtrees`` will be non-None.
+
+    .. attribute:: excluded_subtrees
+
+        :type: list of :class:`GeneralName` objects or None
+
+        Any name matching a restriction in the ``excluded_subtrees`` field is
+        invalid regardless of information appearing in the
+        ``permitted_subtrees``. At least one of ``permitted_subtrees`` and
+        ``excluded_subtrees`` will be non-None.
+
 .. class:: AuthorityKeyIdentifier
 
     .. versionadded:: 0.9
@@ -1369,6 +1395,11 @@
     Corresponds to the dotted string ``"2.5.29.14"``. The identifier for the
     :class:`SubjectKeyIdentifier` extension type.
 
+.. data:: OID_NAME_CONSTRAINTS
+
+    Corresponds to the dotted string ``"2.5.29.30"``. The identifier for the
+    :class:`NameConstraints` extension type.
+
 .. data:: OID_CRL_DISTRIBUTION_POINTS
 
     Corresponds to the dotted string ``"2.5.29.31"``. The identifier for the
diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py
index 2e2e851..4dbe3da 100644
--- a/src/cryptography/x509.py
+++ b/src/cryptography/x509.py
@@ -676,6 +676,58 @@
         return not self == other
 
 
+class NameConstraints(object):
+    def __init__(self, permitted_subtrees, excluded_subtrees):
+        if permitted_subtrees is not None:
+            if not all(
+                isinstance(x, GeneralName) for x in permitted_subtrees
+            ):
+                raise TypeError(
+                    "permitted_subtrees must be a list of GeneralName objects "
+                    "or None"
+                )
+
+            self._validate_ip_name(permitted_subtrees)
+
+        if excluded_subtrees is not None:
+            if not all(
+                isinstance(x, GeneralName) for x in excluded_subtrees
+            ):
+                raise TypeError(
+                    "excluded_subtrees must be a list of GeneralName objects "
+                    "or None"
+                )
+
+            self._validate_ip_name(excluded_subtrees)
+
+        if permitted_subtrees is None and excluded_subtrees is None:
+            raise ValueError(
+                "At least one of permitted_subtrees and excluded_subtrees "
+                "must not be None"
+            )
+
+        self._permitted_subtrees = permitted_subtrees
+        self._excluded_subtrees = excluded_subtrees
+
+    def _validate_ip_name(self, tree):
+        if any(isinstance(name, IPAddress) and not isinstance(
+            name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)
+        ) for name in tree):
+            raise TypeError(
+                "IPAddress name constraints must be an IPv4Network or"
+                " IPv6Network object"
+            )
+
+    def __repr__(self):
+        return (
+            u"<NameConstraints(permitted_subtrees={0.permitted_subtrees}, "
+            u"excluded_subtrees={0.excluded_subtrees})>".format(self)
+        )
+
+    permitted_subtrees = utils.read_only_property("_permitted_subtrees")
+    excluded_subtrees = utils.read_only_property("_excluded_subtrees")
+
+
 class CRLDistributionPoints(object):
     def __init__(self, distribution_points):
         if not all(
diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py
index 62d9f83..a5747c3 100644
--- a/tests/test_x509_ext.py
+++ b/tests/test_x509_ext.py
@@ -1905,6 +1905,74 @@
         assert ext.value.authority_cert_serial_number == 3
 
 
+class TestNameConstraints(object):
+    def test_ipaddress_wrong_type(self):
+        with pytest.raises(TypeError):
+            x509.NameConstraints(
+                permitted_subtrees=[
+                    x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1"))
+                ],
+                excluded_subtrees=None
+            )
+
+        with pytest.raises(TypeError):
+            x509.NameConstraints(
+                permitted_subtrees=None,
+                excluded_subtrees=[
+                    x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1"))
+                ]
+            )
+
+    def test_ipaddress_allowed_type(self):
+        permitted = [x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/29"))]
+        excluded = [x509.IPAddress(ipaddress.IPv4Network(u"10.10.0.0/24"))]
+        nc = x509.NameConstraints(
+            permitted_subtrees=permitted,
+            excluded_subtrees=excluded
+        )
+        assert nc.permitted_subtrees == permitted
+        assert nc.excluded_subtrees == excluded
+
+    def test_invalid_permitted_subtrees(self):
+        with pytest.raises(TypeError):
+            x509.NameConstraints("badpermitted", None)
+
+    def test_invalid_excluded_subtrees(self):
+        with pytest.raises(TypeError):
+            x509.NameConstraints(None, "badexcluded")
+
+    def test_no_subtrees(self):
+        with pytest.raises(ValueError):
+            x509.NameConstraints(None, None)
+
+    def test_permitted_none(self):
+        excluded = [x509.DNSName(u"name.local")]
+        nc = x509.NameConstraints(
+            permitted_subtrees=None, excluded_subtrees=excluded
+        )
+        assert nc.permitted_subtrees is None
+        assert nc.excluded_subtrees is not None
+
+    def test_excluded_none(self):
+        permitted = [x509.DNSName(u"name.local")]
+        nc = x509.NameConstraints(
+            permitted_subtrees=permitted, excluded_subtrees=None
+        )
+        assert nc.permitted_subtrees is not None
+        assert nc.excluded_subtrees is None
+
+    def test_repr(self):
+        permitted = [x509.DNSName(u"name.local"), x509.DNSName(u"name2.local")]
+        nc = x509.NameConstraints(
+            permitted_subtrees=permitted,
+            excluded_subtrees=None
+        )
+        assert repr(nc) == (
+            "<NameConstraints(permitted_subtrees=[<DNSName(value=name.local)>"
+            ", <DNSName(value=name2.local)>], excluded_subtrees=None)>"
+        )
+
+
 class TestDistributionPoint(object):
     def test_distribution_point_full_name_not_general_names(self):
         with pytest.raises(TypeError):