Add concurrent X.509 CRT verification test

This commit enhances the X.509 parsing test suite by a test
which exercises multiple threads concurrently verifying the
same certificate with the same set of trusted roots.
diff --git a/tests/suites/test_suite_x509parse.function b/tests/suites/test_suite_x509parse.function
index 2df187d..ffd9376 100644
--- a/tests/suites/test_suite_x509parse.function
+++ b/tests/suites/test_suite_x509parse.function
@@ -58,6 +58,44 @@
     1024,
 };
 
+
+typedef struct
+{
+    mbedtls_x509_crt *crt;
+    mbedtls_x509_crt *ca;
+    uint32_t expected_flags;
+    unsigned id;
+    int expected_result;
+    int iter_total;
+    int result;
+} x509_verify_thread_ctx;
+
+void* x509_verify_thread_worker( void *p )
+{
+    unsigned iter_cnt;
+    x509_verify_thread_ctx *ctx = (x509_verify_thread_ctx *) p;
+
+    for( iter_cnt=0; iter_cnt < (unsigned) ctx->iter_total; iter_cnt++ )
+    {
+        uint32_t flags;
+        int res;
+
+        res = mbedtls_x509_crt_verify_with_profile( ctx->crt, ctx->ca,
+                                                    NULL, &compat_profile,
+                                                    NULL, &flags, NULL, NULL );
+        if( res   != ctx->expected_result ||
+            flags != ctx->expected_flags )
+        {
+            ctx->result = 1;
+            pthread_exit( NULL );
+        }
+    }
+
+    ctx->result = 0;
+    pthread_exit( NULL );
+    return( NULL );
+}
+
 int verify_none( void *data, mbedtls_x509_crt *crt, int certificate_depth, uint32_t *flags )
 {
     ((void) data);
@@ -352,6 +390,62 @@
 }
 /* END_CASE */
 
+/* BEGIN_CASE depends_on:MBEDTLS_THREADING_PTHREAD:MBEDTLS_FS_IO:MBEDTLS_X509_CRT_PARSE_C */
+void x509_verify_thread( char *crt_file, char *ca_file,
+                         int result, int flags_result,
+                         int thread_total,
+                         int iterations_per_thread )
+{
+    x509_verify_thread_ctx *thread_ctx;
+    pthread_t *threads;
+    int cur_thread;
+
+    mbedtls_x509_crt crt;
+    mbedtls_x509_crt ca;
+
+#if defined(MBEDTLS_USE_PSA_CRYPTO)
+    TEST_ASSERT( psa_crypto_init() == 0 );
+#endif
+
+    mbedtls_x509_crt_init( &crt );
+    mbedtls_x509_crt_init( &ca );
+    threads = mbedtls_calloc( thread_total, sizeof( pthread_t ) );
+    thread_ctx = mbedtls_calloc( thread_total, sizeof( x509_verify_thread_ctx ) );
+
+    TEST_ASSERT( mbedtls_x509_crt_parse_file( &crt, crt_file ) == 0 );
+    TEST_ASSERT( mbedtls_x509_crt_parse_file( &ca, ca_file ) == 0 );
+    TEST_ASSERT( threads != NULL );
+
+    /* Start all verify threads */
+    for( cur_thread = 0; cur_thread < thread_total; cur_thread++ )
+    {
+        thread_ctx[ cur_thread ].id = (unsigned) cur_thread;
+        thread_ctx[ cur_thread ].ca  = &ca;
+        thread_ctx[ cur_thread ].crt = &crt;
+        thread_ctx[ cur_thread ].expected_result = result;
+        thread_ctx[ cur_thread ].expected_flags = flags_result;
+        thread_ctx[ cur_thread ].iter_total = iterations_per_thread;
+        TEST_ASSERT( pthread_create( &threads[ cur_thread ], NULL,
+                                     &x509_verify_thread_worker,
+                                     &thread_ctx[ cur_thread ] ) == 0 );
+    }
+
+    /* Wait for all threads to complete */
+    for( cur_thread = 0; cur_thread < thread_total; cur_thread++ )
+        TEST_ASSERT( pthread_join( threads[ cur_thread ], NULL ) == 0 );
+
+    /* Check their results */
+    for( cur_thread = 0; cur_thread < thread_total; cur_thread++ )
+        TEST_ASSERT( thread_ctx[ cur_thread ].result == 0 );
+
+exit:
+    mbedtls_free( threads );
+    mbedtls_free( thread_ctx );
+    mbedtls_x509_crt_free( &crt );
+    mbedtls_x509_crt_free( &ca );
+}
+/* END_CASE */
+
 /* BEGIN_CASE depends_on:MBEDTLS_FS_IO:MBEDTLS_X509_CRT_PARSE_C:MBEDTLS_X509_CRL_PARSE_C */
 void x509_verify( char *crt_file, char *ca_file, char *crl_file,
                   char *cn_name_str, int result, int flags_result,