Repository: pyca/cryptography

Issue:
ValueError in .rfc4514_string()
Using `cryptography==2.8` and calling `.rfc4514_string()` on a subject/issuer containing empty strings raises a `ValueError` exception.

https://github.com/pyca/cryptography/blob/2.8/src/cryptography/x509/name.py#L96-L97

Is there any reason for doing so? I know this might not be good practice, but it prevents a quick and easy way to display some certificates. For example, this one from `code.jquery.com`:

https://censys.io/certificates/23723c81a723301885ba8825da77c145f84c1e181cc9ec94899b0f3dc33bbf3e

```
C=US, ST=, L=, O=jquery, OU=Certificate Authority, CN=code.jquery.com
ValueError in .rfc4514_string()
Using `cryptography==2.8` and calling `.rfc4514_string()` on a subject/issuer containing empty strings raises a `ValueError` exception.

https://github.com/pyca/cryptography/blob/2.8/src/cryptography/x509/name.py#L96-L97

Is there any reason for doing so? I know this might not be good practice, but it prevents a quick and easy way to display some certificates. For example, this one from `code.jquery.com`:

https://censys.io/certificates/23723c81a723301885ba8825da77c145f84c1e181cc9ec94899b0f3dc33bbf3e

```
C=US, ST=, L=, O=jquery, OU=Certificate Authority, CN=code.jquery.com


Discussion:
No this is a bug, we’d be happy to take a PR to fix this!
No this is a bug, we’d be happy to take a PR to fix this!

Resolving patch:
diff --git a/src/cryptography/x509/name.py b/src/cryptography/x509/name.py
--- a/src/cryptography/x509/name.py
+++ b/src/cryptography/x509/name.py
@@ -54,6 +54,9 @@ class _ASN1Type(Enum):
 def _escape_dn_value(val):
     """Escape special characters in RFC4514 Distinguished Name value."""
 
+    if not val:
+        return ''
+
     # See https://tools.ietf.org/html/rfc4514#section-2.4
     val = val.replace('\\', '\\\\')
     val = val.replace('"', '\\"')
@@ -93,9 +96,6 @@ def __init__(self, oid, value, _type=_SENTINEL):
                     "Country name must be a 2 character country code"
                 )
 
-        if len(value) == 0:
-            raise ValueError("Value cannot be an empty string")
-
         # The appropriate ASN1 string type varies by OID and is defined across
         # multiple RFCs including 2459, 3280, and 5280. In general UTF8String
         # is preferred (2459), but 3280 and 5280 specify several OIDs with


Test patch:
diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py
--- a/tests/x509/test_x509.py
+++ b/tests/x509/test_x509.py
@@ -4281,6 +4281,10 @@ def test_init_bad_value(self):
                 b'bytes'
             )
 
+    def test_init_none_value(self):
+        with pytest.raises(TypeError):
+            x509.NameAttribute(NameOID.ORGANIZATION_NAME, None)
+
     def test_init_bad_country_code_value(self):
         with pytest.raises(ValueError):
             x509.NameAttribute(
@@ -4295,10 +4299,6 @@ def test_init_bad_country_code_value(self):
                 u'\U0001F37A\U0001F37A'
             )
 
-    def test_init_empty_value(self):
-        with pytest.raises(ValueError):
-            x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'')
-
     def test_invalid_type(self):
         with pytest.raises(TypeError):
             x509.NameAttribute(NameOID.COMMON_NAME, u"common", "notanenum")
@@ -4350,6 +4350,10 @@ def test_distinugished_name(self):
         assert (na.rfc4514_string() ==
                 '1.2.840.113549.1.9.1=somebody@example.com')
 
+    def test_empty_value(self):
+        na = x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'')
+        assert na.rfc4514_string() == r'ST='
+
 
 class TestRelativeDistinguishedName(object):
     def test_init_empty(self):
@@ -4569,6 +4573,16 @@ def test_rfc4514_string(self):
         assert (n.rfc4514_string() ==
                 'OU=Sales+CN=J.  Smith,DC=example,DC=net')
 
+    def test_rfc4514_string_empty_values(self):
+        n = x509.Name([
+            x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'),
+            x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u''),
+            x509.NameAttribute(NameOID.LOCALITY_NAME, u''),
+            x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'),
+            x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'),
+        ])
+        assert (n.rfc4514_string() == 'CN=cryptography.io,O=PyCA,L=,ST=,C=US')
+
     def test_not_nameattribute(self):
         with pytest.raises(TypeError):
             x509.Name(["not-a-NameAttribute"])