Name: add support for multi-value RDNs (#3202)

Update the Name class to accept and internally store a list of
RelativeDistinguishedName objects.  Add the 'rdns' attribute to give
access to the RDNs.  Update ASN.1 routines to correctly decode and
encode multi-value RDNs.

Fixes: https://github.com/pyca/cryptography/issues/3199
diff --git a/AUTHORS.rst b/AUTHORS.rst
index f825c2c..44dec5b 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -33,3 +33,4 @@
 * Maximilian Hils <max@maximilianhils.com>
 * Simo Sorce <simo@redhat.com>
 * Thomas Sileo <t@a4.io>
+* Fraser Tweedale <ftweedal@redhat.com>
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index f241c96..4a9b88c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -32,6 +32,12 @@
   :attr:`~cryptography.x509.DistributionPoint.relative_name`.
   Deprecated use of :class:`~cryptography.x509.Name` as
   :attr:`~cryptography.x509.DistributionPoint.relative_name`.
+* :class:`~cryptography.x509.Name` now accepts an iterable of
+  :class:`~cryptography.x509.RelativeDistinguishedName`.  RDNs can
+  be accessed via the :attr:`~cryptography.x509.Name.rdns`
+  attribute.  When constructed with an iterable of
+  :class:`~cryptography.x509.NameAttribute`, each attribute becomes
+  a single-valued RDN.
 
 1.5.3 - 2016-11-05
 ~~~~~~~~~~~~~~~~~~
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst
index c562331..ce479a7 100644
--- a/docs/x509/reference.rst
+++ b/docs/x509/reference.rst
@@ -1102,6 +1102,18 @@
     slash or comma delimited string (e.g. ``/CN=mydomain.com/O=My Org/C=US`` or
     ``CN=mydomain.com, O=My Org, C=US``).
 
+    Technically, a Name is a list of *sets* of attributes, called *Relative
+    Distinguished Names* or *RDNs*, although multi-valued RDNs are rarely
+    encountered.  The iteration order of values within a multi-valued RDN is
+    undefined.  If you need to handle multi-valued RDNs, the ``rdns`` property
+    gives access to an ordered list of :class:`RelativeDistinguishedName`
+    objects.
+
+    A Name can be initialized with an iterable of :class:`NameAttribute` (the
+    common case where each RDN has a single attribute) or an iterable of
+    :class:`RelativeDistinguishedName` objects (in the rare case of
+    multi-valued RDNs).
+
     .. doctest::
 
         >>> len(cert.subject)
@@ -1112,6 +1124,12 @@
         <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.10, name=organizationName)>, value=u'Test Certificates 2011')>
         <NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value=u'Good CA')>
 
+    .. attribute:: rdns
+
+        .. versionadded:: 1.6
+
+        :type: list of :class:`RelativeDistinguishedName`
+
     .. method:: get_attributes_for_oid(oid)
 
         :param oid: An :class:`ObjectIdentifier` instance.
@@ -1142,7 +1160,8 @@
 
     .. versionadded:: 0.8
 
-    An X.509 name consists of a list of NameAttribute instances.
+    An X.509 name consists of a list of :class:`RelativeDistinguishedName`
+    instances, which consist of a set of :class:`NameAttribute` instances.
 
     .. attribute:: oid
 
diff --git a/src/_cffi_src/openssl/x509name.py b/src/_cffi_src/openssl/x509name.py
index 30acbdb..0554a02 100644
--- a/src/_cffi_src/openssl/x509name.py
+++ b/src/_cffi_src/openssl/x509name.py
@@ -35,6 +35,7 @@
 int X509_NAME_get_index_by_NID(X509_NAME *, int, int);
 int X509_NAME_cmp(const X509_NAME *, const X509_NAME *);
 X509_NAME *X509_NAME_dup(X509_NAME *);
+int Cryptography_X509_NAME_ENTRY_set(X509_NAME_ENTRY *);
 """
 
 MACROS = """
@@ -76,4 +77,13 @@
 """
 
 CUSTOMIZATIONS = """
+#if CRYPTOGRAPHY_OPENSSL_110_OR_GREATER && !defined(LIBRESSL_VERSION_NUMBER)
+int Cryptography_X509_NAME_ENTRY_set(X509_NAME_ENTRY *ne) {
+    return X509_NAME_ENTRY_set(ne);
+}
+#else
+int Cryptography_X509_NAME_ENTRY_set(X509_NAME_ENTRY *ne) {
+    return ne->set;
+}
+#endif
 """
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 7efab2b..41e7e77 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -820,7 +820,7 @@
 
         # Set the subject's name.
         res = self._lib.X509_set_subject_name(
-            x509_cert, _encode_name_gc(self, list(builder._subject_name))
+            x509_cert, _encode_name_gc(self, builder._subject_name)
         )
         self.openssl_assert(res == 1)
 
@@ -860,7 +860,7 @@
 
         # Set the issuer name.
         res = self._lib.X509_set_issuer_name(
-            x509_cert, _encode_name_gc(self, list(builder._issuer_name))
+            x509_cert, _encode_name_gc(self, builder._issuer_name)
         )
         self.openssl_assert(res == 1)
 
@@ -911,7 +911,7 @@
 
         # Set the issuer name.
         res = self._lib.X509_CRL_set_issuer_name(
-            x509_crl, _encode_name_gc(self, list(builder._issuer_name))
+            x509_crl, _encode_name_gc(self, builder._issuer_name)
         )
         self.openssl_assert(res == 1)
 
diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py
index f8e8c95..2cbc349 100644
--- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py
+++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py
@@ -45,11 +45,19 @@
 def _decode_x509_name(backend, x509_name):
     count = backend._lib.X509_NAME_entry_count(x509_name)
     attributes = []
+    prev_set_id = -1
     for x in range(count):
         entry = backend._lib.X509_NAME_get_entry(x509_name, x)
-        attributes.append(_decode_x509_name_entry(backend, entry))
+        attribute = _decode_x509_name_entry(backend, entry)
+        set_id = backend._lib.Cryptography_X509_NAME_ENTRY_set(entry)
+        if set_id != prev_set_id:
+            attributes.append(set([attribute]))
+        else:
+            # is in the same RDN a previous entry
+            attributes[-1].add(attribute)
+        prev_set_id = set_id
 
-    return x509.Name(attributes)
+    return x509.Name(x509.RelativeDistinguishedName(rdn) for rdn in attributes)
 
 
 def _decode_general_names(backend, gns):
@@ -552,13 +560,13 @@
             else:
                 rns = cdp.distpoint.name.relativename
                 rnum = backend._lib.sk_X509_NAME_ENTRY_num(rns)
-                attributes = []
+                attributes = set()
                 for i in range(rnum):
                     rn = backend._lib.sk_X509_NAME_ENTRY_value(
                         rns, i
                     )
                     backend.openssl_assert(rn != backend._ffi.NULL)
-                    attributes.append(
+                    attributes.add(
                         _decode_x509_name_entry(backend, rn)
                     )
 
diff --git a/src/cryptography/hazmat/backends/openssl/encode_asn1.py b/src/cryptography/hazmat/backends/openssl/encode_asn1.py
index 284c760..3b78486 100644
--- a/src/cryptography/hazmat/backends/openssl/encode_asn1.py
+++ b/src/cryptography/hazmat/backends/openssl/encode_asn1.py
@@ -79,19 +79,23 @@
     return _encode_asn1_int_gc(backend, inhibit_any_policy.skip_certs)
 
 
-def _encode_name(backend, attributes):
+def _encode_name(backend, name):
     """
     The X509_NAME created will not be gc'd. Use _encode_name_gc if needed.
     """
     subject = backend._lib.X509_NAME_new()
-    for attribute in attributes:
-        name_entry = _encode_name_entry(backend, attribute)
-        # X509_NAME_add_entry dups the object so we need to gc this copy
-        name_entry = backend._ffi.gc(
-            name_entry, backend._lib.X509_NAME_ENTRY_free
-        )
-        res = backend._lib.X509_NAME_add_entry(subject, name_entry, -1, 0)
-        backend.openssl_assert(res == 1)
+    for rdn in name.rdns:
+        set_flag = 0  # indicate whether to add to last RDN or create new RDN
+        for attribute in rdn:
+            name_entry = _encode_name_entry(backend, attribute)
+            # X509_NAME_add_entry dups the object so we need to gc this copy
+            name_entry = backend._ffi.gc(
+                name_entry, backend._lib.X509_NAME_ENTRY_free
+            )
+            res = backend._lib.X509_NAME_add_entry(
+                    subject, name_entry, -1, set_flag)
+            backend.openssl_assert(res == 1)
+            set_flag = -1
     return subject
 
 
diff --git a/src/cryptography/x509/name.py b/src/cryptography/x509/name.py
index 7dc8064..fedfd78 100644
--- a/src/cryptography/x509/name.py
+++ b/src/cryptography/x509/name.py
@@ -90,14 +90,25 @@
 class Name(object):
     def __init__(self, attributes):
         attributes = list(attributes)
-        if not all(isinstance(x, NameAttribute) for x in attributes):
-            raise TypeError("attributes must be a list of NameAttribute")
-
-        self._attributes = attributes
+        if all(isinstance(x, NameAttribute) for x in attributes):
+            self._attributes = [
+                RelativeDistinguishedName([x]) for x in attributes
+            ]
+        elif all(isinstance(x, RelativeDistinguishedName) for x in attributes):
+            self._attributes = attributes
+        else:
+            raise TypeError(
+                "attributes must be a list of NameAttribute"
+                " or a list RelativeDistinguishedName"
+            )
 
     def get_attributes_for_oid(self, oid):
         return [i for i in self if i.oid == oid]
 
+    @property
+    def rdns(self):
+        return self._attributes
+
     def __eq__(self, other):
         if not isinstance(other, Name):
             return NotImplemented
@@ -113,10 +124,12 @@
         return hash(tuple(self._attributes))
 
     def __iter__(self):
-        return iter(self._attributes)
+        for rdn in self._attributes:
+            for ava in rdn:
+                yield ava
 
     def __len__(self):
-        return len(self._attributes)
+        return sum(len(rdn) for rdn in self._attributes)
 
     def __repr__(self):
-        return "<Name({0!r})>".format(self._attributes)
+        return "<Name({0!r})>".format(list(self))
diff --git a/tests/test_x509.py b/tests/test_x509.py
index 67df30c..6a999f4 100644
--- a/tests/test_x509.py
+++ b/tests/test_x509.py
@@ -2641,6 +2641,30 @@
         ]
 
     @pytest.mark.requires_backend_interface(interface=RSABackend)
+    def test_build_ca_request_with_multivalue_rdns(self, backend):
+        private_key = RSA_KEY_2048.private_key(backend)
+        subject = x509.Name([
+            x509.RelativeDistinguishedName([
+                x509.NameAttribute(NameOID.TITLE, u'Test'),
+                x509.NameAttribute(NameOID.COMMON_NAME, u'Multivalue'),
+                x509.NameAttribute(NameOID.SURNAME, u'RDNs'),
+            ]),
+            x509.RelativeDistinguishedName([
+                x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA')
+            ]),
+        ])
+
+        request = x509.CertificateSigningRequestBuilder().subject_name(
+            subject
+        ).sign(private_key, hashes.SHA1(), backend)
+
+        loaded_request = x509.load_pem_x509_csr(
+            request.public_bytes(encoding=serialization.Encoding.PEM), backend
+        )
+        assert isinstance(loaded_request.subject, x509.Name)
+        assert loaded_request.subject == subject
+
+    @pytest.mark.requires_backend_interface(interface=RSABackend)
     def test_build_nonca_request_with_rsa(self, backend):
         private_key = RSA_KEY_2048.private_key(backend)
 
@@ -3724,44 +3748,43 @@
 
 class TestName(object):
     def test_eq(self):
-        name1 = x509.Name([
-            x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'),
-            x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'),
-        ])
+        ava1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1')
+        ava2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2')
+        name1 = x509.Name([ava1, ava2])
         name2 = x509.Name([
-            x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'),
-            x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'),
+            x509.RelativeDistinguishedName([ava1]),
+            x509.RelativeDistinguishedName([ava2]),
         ])
+        name3 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])])
+        name4 = x509.Name([x509.RelativeDistinguishedName([ava2, ava1])])
         assert name1 == name2
+        assert name3 == name4
 
     def test_ne(self):
-        name1 = x509.Name([
-            x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'),
-            x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'),
-        ])
-        name2 = x509.Name([
-            x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'),
-            x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'),
-        ])
+        ava1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1')
+        ava2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2')
+        name1 = x509.Name([ava1, ava2])
+        name2 = x509.Name([ava2, ava1])
+        name3 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])])
         assert name1 != name2
+        assert name1 != name3
         assert name1 != object()
 
     def test_hash(self):
-        name1 = x509.Name([
-            x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'),
-            x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'),
-        ])
+        ava1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1')
+        ava2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2')
+        name1 = x509.Name([ava1, ava2])
         name2 = x509.Name([
-            x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'),
-            x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'),
+            x509.RelativeDistinguishedName([ava1]),
+            x509.RelativeDistinguishedName([ava2]),
         ])
-        name3 = x509.Name([
-            x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'),
-            x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'),
-        ])
-
+        name3 = x509.Name([ava2, ava1])
+        name4 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])])
+        name5 = x509.Name([x509.RelativeDistinguishedName([ava2, ava1])])
         assert hash(name1) == hash(name2)
         assert hash(name1) != hash(name3)
+        assert hash(name1) != hash(name4)
+        assert hash(name4) == hash(name5)
 
     def test_iter_input(self):
         attrs = [
@@ -3771,6 +3794,17 @@
         assert list(name) == attrs
         assert list(name) == attrs
 
+    def test_rdns(self):
+        rdn1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1')
+        rdn2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2')
+        name1 = x509.Name([rdn1, rdn2])
+        assert name1.rdns == [
+            x509.RelativeDistinguishedName([rdn1]),
+            x509.RelativeDistinguishedName([rdn2]),
+        ]
+        name2 = x509.Name([x509.RelativeDistinguishedName([rdn1, rdn2])])
+        assert name2.rdns == [x509.RelativeDistinguishedName([rdn1, rdn2])]
+
     def test_repr(self):
         name = x509.Name([
             x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'),