Add mbedtls_x509_dn_get_next function

Allow iteration through relative DNs when X509 name contains multi-
value RDNs.

Signed-off-by: Werner Lewis <werner.lewis@arm.com>
diff --git a/ChangeLog.d/add_dn_get_next.txt b/ChangeLog.d/add_dn_get_next.txt
new file mode 100644
index 0000000..04ee954
--- /dev/null
+++ b/ChangeLog.d/add_dn_get_next.txt
@@ -0,0 +1,3 @@
+Bugfix
+   * Add mbedtls_x509_dn_get_next function to return the next relative DN in
+     an X509 name, to allow walking the name list. Fixes #5431.
diff --git a/include/mbedtls/x509.h b/include/mbedtls/x509.h
index 3c76fec..2ad90de 100644
--- a/include/mbedtls/x509.h
+++ b/include/mbedtls/x509.h
@@ -267,6 +267,16 @@
 int mbedtls_x509_dn_gets( char *buf, size_t size, const mbedtls_x509_name *dn );
 
 /**
+ * \brief          Return the next relative DN in an X509 name.
+ *
+ * \param dn       Current node in the X509 name
+ *
+ * \return         Pointer to the first attribute-value pair of the
+ *                 next RDN in sequence, or NULL if end is reached.
+ */
+mbedtls_x509_name * mbedtls_x509_dn_get_next( mbedtls_x509_name * start );
+
+/**
  * \brief          Store the certificate serial in printable form into buf;
  *                 no more than size characters will be written.
  *
diff --git a/library/x509.c b/library/x509.c
index 2e11c7f..17d1030 100644
--- a/library/x509.c
+++ b/library/x509.c
@@ -797,6 +797,15 @@
 }
 
 /*
+ * Return the next relative DN in an X509 name.
+ */
+mbedtls_x509_name * mbedtls_x509_dn_get_next( mbedtls_x509_name * dn )
+{
+    for( ; dn->next != NULL && dn->next_merged; dn = dn->next );
+    return( dn->next );
+}
+
+/*
  * Store the serial in printable form into buf; no more
  * than size characters will be written
  */
diff --git a/tests/suites/test_suite_x509parse.data b/tests/suites/test_suite_x509parse.data
index d04b7d8..5bb8479 100644
--- a/tests/suites/test_suite_x509parse.data
+++ b/tests/suites/test_suite_x509parse.data
@@ -375,6 +375,18 @@
 depends_on:MBEDTLS_PEM_PARSE_C:MBEDTLS_RSA_C:MBEDTLS_SHA1_C
 mbedtls_x509_dn_gets:"data_files/server2.crt":"issuer":"C=NL, O=PolarSSL, CN=PolarSSL Test CA"
 
+X509 Get Next DN #1 No Multivalue RDNs
+mbedtls_x509_dn_get_next:"C=NL, O=PolarSSL, CN=PolarSSL Server 1":0:"C O CN":3:"C=NL, O=PolarSSL, CN=PolarSSL Server 1"
+
+X509 Get Next DN #2 Initial Multivalue RDN
+mbedtls_x509_dn_get_next:"C=NL, O=PolarSSL, CN=PolarSSL Server 1":0b001:"C CN":2:"C=NL + O=PolarSSL, CN=PolarSSL Server 1"
+
+X509 Get Next DN #3 Single Multivalue RDN
+mbedtls_x509_dn_get_next:"C=NL, O=PolarSSL, CN=PolarSSL Server 1":0b011:"C":1:"C=NL + O=PolarSSL + CN=PolarSSL Server 1"
+
+X509 Get Next DN #4 Consecutive MV RDNs
+mbedtls_x509_dn_get_next:"C=NL, O=PolarSSL, title=Example, CN=PolarSSL Server 1":0b0101:"C title":2:"C=NL + O=PolarSSL, title=Example + CN=PolarSSL Server 1"
+
 X509 Time Expired #1
 depends_on:MBEDTLS_PEM_PARSE_C:MBEDTLS_RSA_C:MBEDTLS_HAVE_TIME_DATE:MBEDTLS_SHA1_C
 mbedtls_x509_time_is_past:"data_files/server1.crt":"valid_from":1
diff --git a/tests/suites/test_suite_x509parse.function b/tests/suites/test_suite_x509parse.function
index 1d06fe3..2495270 100644
--- a/tests/suites/test_suite_x509parse.function
+++ b/tests/suites/test_suite_x509parse.function
@@ -785,6 +785,69 @@
 }
 /* END_CASE */
 
+/* BEGIN_CASE depends_on:MBEDTLS_X509_CREATE_C:MBEDTLS_X509_USE_C:MBEDTLS_X509_CRT_PARSE_C:!MBEDTLS_X509_REMOVE_INFO */
+void mbedtls_x509_dn_get_next( char * name_str, int next_merged, char * expected_oids, int exp_count, char * exp_dn_gets )
+{
+    int ret = 0, i;
+    size_t len = 0;
+    mbedtls_asn1_named_data *names = NULL;
+    mbedtls_x509_name parsed, *parsed_cur, *parsed_prv;
+    unsigned char buf[1024], out[1024], *c;
+    const char *short_name;
+
+    memset( &parsed, 0, sizeof( parsed ) );
+    memset( out, 0, sizeof( out ) );
+    memset( buf, 0, sizeof( buf ) );
+    c = buf + sizeof( buf );
+
+    TEST_ASSERT( mbedtls_x509_string_to_names( &names, name_str ) == 0 );
+
+    ret = mbedtls_x509_write_names( &c, buf, names );
+    TEST_ASSERT( ret > 0 );
+
+    TEST_ASSERT( mbedtls_asn1_get_tag( &c, buf + sizeof( buf ), &len,
+                       MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE ) == 0 );
+    TEST_ASSERT( mbedtls_x509_get_name( &c, buf + sizeof( buf ), &parsed ) == 0 );
+
+    // Iterate over names and set next_merged nodes
+    parsed_cur = &parsed;
+    for( ; next_merged != 0 && parsed_cur != NULL; next_merged = next_merged >> 1 )
+    {
+        parsed_cur->next_merged = next_merged & 0b1;
+        parsed_cur = parsed_cur->next;
+    }
+
+    // Iterate over RDN nodes and print OID of first element to buffer
+    parsed_cur = &parsed;
+    len = 0;
+    for( i = 0; parsed_cur != NULL; i++ )
+    {
+        TEST_ASSERT( mbedtls_oid_get_attr_short_name( &parsed_cur->oid,
+                                                      &short_name ) == 0 );
+        len += mbedtls_snprintf( (char*) out + len, 1024 - len, "%s ", short_name );
+        parsed_cur = mbedtls_x509_dn_get_next( parsed_cur );
+    }
+    out[len-1] = 0;
+
+    TEST_ASSERT( exp_count == i );
+    TEST_ASSERT( strcmp( (char *) out, expected_oids ) == 0 );
+
+    TEST_ASSERT( mbedtls_x509_dn_gets( (char *) out, sizeof( out ), &parsed ) >= 0 );
+    TEST_ASSERT( strcmp( (char *) out, exp_dn_gets ) == 0 );
+exit:
+    mbedtls_asn1_free_named_data_list( &names );
+
+    parsed_cur = parsed.next;
+    while( parsed_cur != 0 )
+    {
+        parsed_prv = parsed_cur;
+        parsed_cur = parsed_cur->next;
+        mbedtls_free( parsed_prv );
+    }
+}
+
+/* END_CASE */
+
 /* BEGIN_CASE depends_on:MBEDTLS_FS_IO:MBEDTLS_X509_CRT_PARSE_C */
 void mbedtls_x509_time_is_past( char * crt_file, char * entity, int result )
 {