Merge pull request #5546 from SiliconLabs/mbedtls-2.28/feature/PSEC-3195-PSA-test-suites-NOT-using-UID-0

Backport 2.28: feat: Update test_suite_psa_its to NOT use UID=0
diff --git a/ChangeLog.d/use-psa-ecdhe-curve.txt b/ChangeLog.d/use-psa-ecdhe-curve.txt
new file mode 100644
index 0000000..cc432bd
--- /dev/null
+++ b/ChangeLog.d/use-psa-ecdhe-curve.txt
@@ -0,0 +1,7 @@
+Bugfix
+   * Fix a bug in (D)TLS curve negotiation: when MBEDTLS_USE_PSA_CRYPTO was
+     enabled and an ECDHE-ECDSA or ECDHE-RSA key exchange was used, the
+     client would fail to check that the curve selected by the server for
+     ECDHE was indeed one that was offered. As a result, the client would
+     accept any curve that it supported, even if that curve was not allowed
+     according to its configuration.
diff --git a/include/mbedtls/ssl_internal.h b/include/mbedtls/ssl_internal.h
index 6913dc0..f50cf9f 100644
--- a/include/mbedtls/ssl_internal.h
+++ b/include/mbedtls/ssl_internal.h
@@ -1112,6 +1112,7 @@
 
 #if defined(MBEDTLS_ECP_C)
 int mbedtls_ssl_check_curve( const mbedtls_ssl_context *ssl, mbedtls_ecp_group_id grp_id );
+int mbedtls_ssl_check_curve_tls_id( const mbedtls_ssl_context *ssl, uint16_t tls_id );
 #endif
 
 #if defined(MBEDTLS_KEY_EXCHANGE_WITH_CERT_ENABLED)
diff --git a/library/ssl_cli.c b/library/ssl_cli.c
index b87879c..ea85ced 100644
--- a/library/ssl_cli.c
+++ b/library/ssl_cli.c
@@ -2703,6 +2703,10 @@
     tls_id <<= 8;
     tls_id |= *(*p)++;
 
+    /* Check it's a curve we offered */
+    if( mbedtls_ssl_check_curve_tls_id( ssl, tls_id ) != 0 )
+        return( MBEDTLS_ERR_SSL_BAD_HS_SERVER_KEY_EXCHANGE );
+
     /* Convert EC group to PSA key type. */
     if( ( handshake->ecdh_psa_type =
           mbedtls_psa_parse_tls_ecc_group( tls_id, &ecdh_bits ) ) == 0 )
diff --git a/library/ssl_tls.c b/library/ssl_tls.c
index c7265f1..bd0eb10 100644
--- a/library/ssl_tls.c
+++ b/library/ssl_tls.c
@@ -7326,6 +7326,18 @@
 
     return( -1 );
 }
+
+/*
+ * Same as mbedtls_ssl_check_curve() but takes a TLS ID for the curve.
+ */
+int mbedtls_ssl_check_curve_tls_id( const mbedtls_ssl_context *ssl, uint16_t tls_id )
+{
+    const mbedtls_ecp_curve_info *curve_info =
+        mbedtls_ecp_curve_info_from_tls_id( tls_id );
+    if( curve_info == NULL )
+        return( -1 );
+    return( mbedtls_ssl_check_curve( ssl, curve_info->grp_id ) );
+}
 #endif /* MBEDTLS_ECP_C */
 
 #if defined(MBEDTLS_KEY_EXCHANGE_WITH_CERT_ENABLED)
diff --git a/scripts/ci.requirements.txt b/scripts/ci.requirements.txt
index 18b40ec..338b14f 100644
--- a/scripts/ci.requirements.txt
+++ b/scripts/ci.requirements.txt
@@ -1,5 +1,9 @@
 # Python package requirements for Mbed TLS testing.
 
+# Any package used by a script in this repository must be listed here
+# or in one of the included files. Normally there should be a minimum
+# version constraint; the CI will test with the minimum version.
+
 # Use a known version of Pylint, because new versions tend to add warnings
 # that could start rejecting our code.
 # 2.4.4 is the version in Ubuntu 20.04. It supports Python >=3.5.
diff --git a/scripts/maintainer.requirements.txt b/scripts/maintainer.requirements.txt
index b149921..8734140 100644
--- a/scripts/maintainer.requirements.txt
+++ b/scripts/maintainer.requirements.txt
@@ -1,4 +1,5 @@
-# Python packages that are only useful to Mbed TLS maintainers.
+# Python packages that are not used by any script in this repository,
+# but are likely to be useful to Mbed TLS maintainers.
 
 -r ci.requirements.txt
 
diff --git a/tests/scripts/analyze_outcomes.py b/tests/scripts/analyze_outcomes.py
index 73f16bd..d06a059 100755
--- a/tests/scripts/analyze_outcomes.py
+++ b/tests/scripts/analyze_outcomes.py
@@ -7,7 +7,6 @@
 """
 
 import argparse
-import re
 import sys
 import traceback
 
@@ -51,29 +50,9 @@
         """
         return len(self.successes) + len(self.failures)
 
-class TestDescriptions(check_test_cases.TestDescriptionExplorer):
-    """Collect the available test cases."""
-
-    def __init__(self):
-        super().__init__()
-        self.descriptions = set()
-
-    def process_test_case(self, _per_file_state,
-                          file_name, _line_number, description):
-        """Record an available test case."""
-        base_name = re.sub(r'\.[^.]*$', '', re.sub(r'.*/', '', file_name))
-        key = ';'.join([base_name, description.decode('utf-8')])
-        self.descriptions.add(key)
-
-def collect_available_test_cases():
-    """Collect the available test cases."""
-    explorer = TestDescriptions()
-    explorer.walk_all()
-    return sorted(explorer.descriptions)
-
 def analyze_coverage(results, outcomes):
     """Check that all available test cases are executed at least once."""
-    available = collect_available_test_cases()
+    available = check_test_cases.collect_available_test_cases()
     for key in available:
         hits = outcomes[key].hits() if key in outcomes else 0
         if hits == 0:
diff --git a/tests/scripts/check_test_cases.py b/tests/scripts/check_test_cases.py
index fe11f20..c9f5e11 100755
--- a/tests/scripts/check_test_cases.py
+++ b/tests/scripts/check_test_cases.py
@@ -134,6 +134,26 @@
             if os.path.exists(ssl_opt_sh):
                 self.walk_ssl_opt_sh(ssl_opt_sh)
 
+class TestDescriptions(TestDescriptionExplorer):
+    """Collect the available test cases."""
+
+    def __init__(self):
+        super().__init__()
+        self.descriptions = set()
+
+    def process_test_case(self, _per_file_state,
+                          file_name, _line_number, description):
+        """Record an available test case."""
+        base_name = re.sub(r'\.[^.]*$', '', re.sub(r'.*/', '', file_name))
+        key = ';'.join([base_name, description.decode('utf-8')])
+        self.descriptions.add(key)
+
+def collect_available_test_cases():
+    """Collect the available test cases."""
+    explorer = TestDescriptions()
+    explorer.walk_all()
+    return sorted(explorer.descriptions)
+
 class DescriptionChecker(TestDescriptionExplorer):
     """Check all test case descriptions.
 
@@ -173,6 +193,9 @@
 
 def main():
     parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument('--list-all',
+                        action='store_true',
+                        help='List all test cases, without doing checks')
     parser.add_argument('--quiet', '-q',
                         action='store_true',
                         help='Hide warnings')
@@ -180,6 +203,10 @@
                         action='store_false', dest='quiet',
                         help='Show warnings (default: on; undoes --quiet)')
     options = parser.parse_args()
+    if options.list_all:
+        descriptions = collect_available_test_cases()
+        sys.stdout.write('\n'.join(descriptions + ['']))
+        return
     results = Results(options)
     checker = DescriptionChecker(results)
     checker.walk_all()