Merge pull request #1972 from reaperhulk/expand-ipaddress

IPAddress needs to support networks for nameconstraints
diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py
index b36694d..6db6fc9 100644
--- a/src/cryptography/hazmat/backends/openssl/x509.py
+++ b/src/cryptography/hazmat/backends/openssl/x509.py
@@ -657,3 +657,35 @@
             raise UnsupportedAlgorithm(
                 "Signature algorithm OID:{0} not recognized".format(oid)
             )
+
+    @property
+    def extensions(self):
+        extensions = []
+        seen_oids = set()
+        x509_exts = self._backend._lib.X509_REQ_get_extensions(self._x509_req)
+        extcount = self._backend._lib.sk_X509_EXTENSION_num(x509_exts)
+        for i in range(0, extcount):
+            ext = self._backend._lib.sk_X509_EXTENSION_value(x509_exts, i)
+            assert ext != self._backend._ffi.NULL
+            crit = self._backend._lib.X509_EXTENSION_get_critical(ext)
+            critical = crit == 1
+            oid = x509.ObjectIdentifier(_obj2txt(self._backend, ext.object))
+            if oid in seen_oids:
+                raise x509.DuplicateExtension(
+                    "Duplicate {0} extension found".format(oid), oid
+                )
+            elif oid == x509.OID_BASIC_CONSTRAINTS:
+                value = _decode_basic_constraints(self._backend, ext)
+            elif critical:
+                raise x509.UnsupportedExtension(
+                    "{0} is not currently supported".format(oid), oid
+                )
+            else:
+                # Unsupported non-critical extension, silently skipping for now
+                seen_oids.add(oid)
+                continue
+
+            seen_oids.add(oid)
+            extensions.append(x509.Extension(oid, critical, value))
+
+        return x509.Extensions(extensions)
diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py
index 17c2a7c..ccb9f6d 100644
--- a/src/cryptography/x509.py
+++ b/src/cryptography/x509.py
@@ -1184,3 +1184,9 @@
         Returns a HashAlgorithm corresponding to the type of the digest signed
         in the certificate.
         """
+
+    @abc.abstractproperty
+    def extensions(self):
+        """
+        Returns the extensions in the signing request.
+        """
diff --git a/tests/test_x509.py b/tests/test_x509.py
index 8561f1f..47c1c64 100644
--- a/tests/test_x509.py
+++ b/tests/test_x509.py
@@ -395,6 +395,9 @@
             x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'PyCA'),
             x509.NameAttribute(x509.OID_COMMON_NAME, 'cryptography.io'),
         ]
+        extensions = request.extensions
+        assert isinstance(extensions, x509.Extensions)
+        assert list(extensions) == []
 
     @pytest.mark.parametrize(
         "loader_func",
@@ -413,6 +416,61 @@
         with pytest.raises(UnsupportedAlgorithm):
             request.signature_hash_algorithm
 
+    def test_duplicate_extension(self, backend):
+        request = _load_cert(
+            os.path.join(
+                "x509", "requests", "two_basic_constraints.pem"
+            ),
+            x509.load_pem_x509_csr,
+            backend
+        )
+        with pytest.raises(x509.DuplicateExtension) as exc:
+            request.extensions
+
+        assert exc.value.oid == x509.OID_BASIC_CONSTRAINTS
+
+    def test_unsupported_critical_extension(self, backend):
+        request = _load_cert(
+            os.path.join(
+                "x509", "requests", "unsupported_extension_critical.pem"
+            ),
+            x509.load_pem_x509_csr,
+            backend
+        )
+        with pytest.raises(x509.UnsupportedExtension) as exc:
+            request.extensions
+
+        assert exc.value.oid == x509.ObjectIdentifier('1.2.3.4')
+
+    def test_unsupported_extension(self, backend):
+        request = _load_cert(
+            os.path.join(
+                "x509", "requests", "unsupported_extension.pem"
+            ),
+            x509.load_pem_x509_csr,
+            backend
+        )
+        extensions = request.extensions
+        assert len(extensions) == 0
+
+    def test_request_basic_constraints(self, backend):
+        request = _load_cert(
+            os.path.join(
+                "x509", "requests", "basic_constraints.pem"
+            ),
+            x509.load_pem_x509_csr,
+            backend
+        )
+        extensions = request.extensions
+        assert isinstance(extensions, x509.Extensions)
+        assert list(extensions) == [
+            x509.Extension(
+                x509.OID_BASIC_CONSTRAINTS,
+                True,
+                x509.BasicConstraints(True, 1),
+            ),
+        ]
+
 
 @pytest.mark.requires_backend_interface(interface=DSABackend)
 @pytest.mark.requires_backend_interface(interface=X509Backend)