Repository: pyca/cryptography

Issue:
x509.Name.rfc4514_string() returns RDNs in wrong order
Hi everyone,

I think the `rfc4514_string()` function of `x509.Name` should return the RDNs in reversed order compared to their 'real' order in i.e. a certificate, because RFC4514 states in section 2.1:

> Otherwise, the output consists of the string encodings of each
> RelativeDistinguishedName in the RDNSequence (according to Section
> 2.2), starting with the last element of the sequence and moving
> backwards toward the first.

I.e. I would expect that a x509.CertificateSigningRequest's subject translates into a string representation (according to RFC4514) like
`"CN=elephant,O=serengeti,C=TZ"`.

The CSR can i.e. be created using OpenSSL:
`openssl req -new -newkey rsa:1024 -keyout test.key -nodes -out test.csr -subj "/C=TZ/O=serengeti/CN=elephant"`.

However doing
```python
from cryptography import x509
from cryptography.hazmat.backends import default_backend

with open('test.csr') as f:
    csr = f.read().encode('ascii')

csr = x509.load_pem_x509_csr(csr, default_backend())
print(csr.subject.rfc4514_string())
```
gives `C=TZ,O=serengeti,CN=elephant`.

The issue - if I understand the RFC correctly and it even is one - is probably located here: https://github.com/pyca/cryptography/blob/master/src/cryptography/x509/name.py#L221 . Inserting a `reversed(...)` should fix it.
Would be great if someone could share her/his opinion on this, and if we all agree I could create a PR. However there is not much point in creating a PR to add a single word, I guess ...



Discussion:
That does appear to be what the RFC says, which would make this a bug. Looks like I missed it during original review (https://github.com/pyca/cryptography/pull/4304). A PR to fix this would be fine, although since our CI is semi-broken (thanks Azure) we won't be able to merge it immediately, sorry.

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
@@ -216,9 +216,11 @@ def rfc4514_string(self):
         An X.509 name is a two-level structure: a list of sets of attributes.
         Each list element is separated by ',' and within each list element, set
         elements are separated by '+'. The latter is almost never used in
-        real world certificates.
+        real world certificates. According to RFC4514 section 2.1 the
+        RDNSequence must be reversed when converting to string representation.
         """
-        return ','.join(attr.rfc4514_string() for attr in self._attributes)
+        return ','.join(
+            attr.rfc4514_string() for attr in reversed(self._attributes))
 
     def get_attributes_for_oid(self, oid):
         return [i for i in self if i.oid == oid]
@@ -253,7 +255,9 @@ def __len__(self):
         return sum(len(rdn) for rdn in self._attributes)
 
     def __repr__(self):
+        rdns = ','.join(attr.rfc4514_string() for attr in self._attributes)
+
         if six.PY2:
-            return "<Name({})>".format(self.rfc4514_string().encode('utf8'))
+            return "<Name({})>".format(rdns.encode('utf8'))
         else:
-            return "<Name({})>".format(self.rfc4514_string())
+            return "<Name({})>".format(rdns)


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
@@ -4556,14 +4556,14 @@ def test_repr(self, common_name, org_name, expected_repr):
     def test_rfc4514_string(self):
         n = x509.Name([
             x509.RelativeDistinguishedName([
-                x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'Sales'),
-                x509.NameAttribute(NameOID.COMMON_NAME, u'J.  Smith'),
+                x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'net'),
             ]),
             x509.RelativeDistinguishedName([
                 x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'example'),
             ]),
             x509.RelativeDistinguishedName([
-                x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'net'),
+                x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'Sales'),
+                x509.NameAttribute(NameOID.COMMON_NAME, u'J.  Smith'),
             ]),
         ])
         assert (n.rfc4514_string() ==