Repository: pyca/cryptography

Issue:
OpenSSL asn1 encoder fails to encode IPv[46]Network
Here's the GeneralName:
`x509.general_name.IPAddress(ipaddress.IPv4Network('127.0.0.0',8))` 

Here's the error:

```
File "/usr/lib64/python3.5/site-packages/cryptography/x509/base.py", line 526, in sign
    return backend.create_x509_certificate(self, private_key, algorithm)
  File "/usr/lib64/python3.5/site-packages/cryptography/hazmat/backends/multibackend.py", line 388, in create_x509_certificate
    return b.create_x509_certificate(builder, private_key, algorithm)
  File "/usr/lib64/python3.5/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 856, in create_x509_certificate
    gc=True
  File "/usr/lib64/python3.5/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 971, in _create_x509_extensions
    handlers, extension
  File "/usr/lib64/python3.5/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1002, in _create_x509_extension
    ext_struct = encode(self, extension.value)
  File "/usr/lib64/python3.5/site-packages/cryptography/hazmat/backends/openssl/encode_asn1.py", line 518, in _encode_name_constraints
    backend, name_constraints.permitted_subtrees
  File "/usr/lib64/python3.5/site-packages/cryptography/hazmat/backends/openssl/encode_asn1.py", line 553, in _encode_general_subtree
    gs.base = _encode_general_name(backend, name)
  File "/usr/lib64/python3.5/site-packages/cryptography/hazmat/backends/openssl/encode_asn1.py", line 397, in _encode_general_name
    backend, name.value.packed, len(name.value.packed)
AttributeError: 'IPv4Network' object has no attribute 'packed'
```

Here's the fix:

``` patch
--- a/src/cryptography/hazmat/backends/openssl/encode_asn1.py
+++ b/src/cryptography/hazmat/backends/openssl/encode_asn1.py
@@ -8,6 +8,8 @@

 import idna

+import ipaddress
+
 import six

 from cryptography import x509
@@ -393,8 +395,20 @@
     elif isinstance(name, x509.IPAddress):
         gn = backend._lib.GENERAL_NAME_new()
         backend.openssl_assert(gn != backend._ffi.NULL)
+        if isinstance(name.value, ipaddress.IPv4Network):
+            packed = (
+                name.value.network_address.packed +
+                ((1<< 32)-name.value.num_addresses).to_bytes( 4, 'big')
+            )
+        elif isinstance(name.value, ipaddress.IPv6Network):
+            packed = (
+                name.value.network_address.packed +
+                ((1<<128)-name.value.num_addresses).to_bytes(16, 'big')
+            )
+        else:
+            packed = name.value.packed
         ipaddr = _encode_asn1_str(
-            backend, name.value.packed, len(name.value.packed)
+            backend, packed, len(packed)
         )
         gn.type = backend._lib.GEN_IPADD
         gn.d.iPAddress = ipaddr
```



Discussion:
So the bug here is that you can decode name constraints with networks, but can't encode them, correct?

Encoding is broken. I haven't tested decoding but the code seems to be there so it probably works.


Resolving patch:
diff --git a/src/cryptography/hazmat/backends/openssl/encode_asn1.py b/src/cryptography/hazmat/backends/openssl/encode_asn1.py
--- a/src/cryptography/hazmat/backends/openssl/encode_asn1.py
+++ b/src/cryptography/hazmat/backends/openssl/encode_asn1.py
@@ -5,12 +5,13 @@
 from __future__ import absolute_import, division, print_function
 
 import calendar
+import ipaddress
 
 import idna
 
 import six
 
-from cryptography import x509
+from cryptography import utils, x509
 from cryptography.hazmat.backends.openssl.decode_asn1 import (
     _CRL_ENTRY_REASON_ENUM_TO_CODE, _DISTPOINT_TYPE_FULLNAME,
     _DISTPOINT_TYPE_RELATIVENAME
@@ -402,9 +403,19 @@ def _encode_general_name(backend, name):
     elif isinstance(name, x509.IPAddress):
         gn = backend._lib.GENERAL_NAME_new()
         backend.openssl_assert(gn != backend._ffi.NULL)
-        ipaddr = _encode_asn1_str(
-            backend, name.value.packed, len(name.value.packed)
-        )
+        if isinstance(name.value, ipaddress.IPv4Network):
+            packed = (
+                name.value.network_address.packed +
+                utils.int_to_bytes(((1 << 32) - name.value.num_addresses), 4)
+            )
+        elif isinstance(name.value, ipaddress.IPv6Network):
+            packed = (
+                name.value.network_address.packed +
+                utils.int_to_bytes((1 << 128) - name.value.num_addresses, 16)
+            )
+        else:
+            packed = name.value.packed
+        ipaddr = _encode_asn1_str(backend, packed, len(packed))
         gn.type = backend._lib.GEN_IPADD
         gn.d.iPAddress = ipaddr
     elif isinstance(name, x509.OtherName):


Test patch:
diff --git a/tests/test_x509.py b/tests/test_x509.py
--- a/tests/test_x509.py
+++ b/tests/test_x509.py
@@ -2405,18 +2405,44 @@ def test_policy_constraints(self, backend, pc):
 
     @pytest.mark.requires_backend_interface(interface=RSABackend)
     @pytest.mark.requires_backend_interface(interface=X509Backend)
-    def test_name_constraints(self, backend):
+    @pytest.mark.parametrize(
+        "nc",
+        [
+            x509.NameConstraints(
+                permitted_subtrees=[
+                    x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/24")),
+                    x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/29")),
+                    x509.IPAddress(ipaddress.IPv4Network(u"127.0.0.1/32")),
+                    x509.IPAddress(ipaddress.IPv4Network(u"8.0.0.0/8")),
+                    x509.IPAddress(ipaddress.IPv4Network(u"0.0.0.0/0")),
+                    x509.IPAddress(
+                        ipaddress.IPv6Network(u"FF:0:0:0:0:0:0:0/96")
+                    ),
+                    x509.IPAddress(
+                        ipaddress.IPv6Network(u"FF:FF:0:0:0:0:0:0/128")
+                    ),
+                ],
+                excluded_subtrees=[x509.DNSName(u"name.local")]
+            ),
+            x509.NameConstraints(
+                permitted_subtrees=[
+                    x509.IPAddress(ipaddress.IPv4Network(u"0.0.0.0/0")),
+                ],
+                excluded_subtrees=None
+            ),
+            x509.NameConstraints(
+                permitted_subtrees=None,
+                excluded_subtrees=[x509.DNSName(u"name.local")]
+            ),
+        ]
+    )
+    def test_name_constraints(self, nc, backend):
         issuer_private_key = RSA_KEY_2048.private_key(backend)
         subject_private_key = RSA_KEY_2048.private_key(backend)
 
         not_valid_before = datetime.datetime(2002, 1, 1, 12, 1)
         not_valid_after = datetime.datetime(2030, 12, 31, 8, 30)
 
-        excluded = [x509.DNSName(u"name.local")]
-        nc = x509.NameConstraints(
-            permitted_subtrees=None, excluded_subtrees=excluded
-        )
-
         cert = x509.CertificateBuilder().subject_name(
             x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')])
         ).issuer_name(