Rework MAC algorithm / key type validation

Reworked the validation of MAC algorithm with the used key type by
introducing psa_mac_key_can_do, which guarantees that PSA_MAC_LENGTH can
be called successfully after validation of the algorithm and key type.

This means psa_get_mac_output_length is no longer required.

Signed-off-by: Steven Cooreman <steven.cooreman@silabs.com>
diff --git a/library/psa_crypto.c b/library/psa_crypto.c
index 13a0835..db4b387 100644
--- a/library/psa_crypto.c
+++ b/library/psa_crypto.c
@@ -545,58 +545,44 @@
     return( slot->attr.bits );
 }
 
-/** Return the output MAC length of a MAC algorithm, in bytes
+/** Check whether a given key type is valid for use with a given MAC algorithm
  *
- * \param[in] algorithm     The specific (non-wildcard) MAC algorithm.
+ * Upon successful return of this function, the behavioud of #PSA_MAC_LENGTH
+ * will be defined when called with the validated \p algorithm and \p key_type
+ *
+ * \param[in] algorithm     The specific MAC algorithm (can be wildcard).
  * \param[in] key_type      The key type of the key to be used with the
  *                          \p algorithm.
- * \param[out] length       The calculated output length of the given MAC
- *                          \p algorithm when used with a key corresponding to
- *                          the given \p key_type
  *
  * \retval #PSA_SUCCESS
- *         The \p length has been successfully calculated
+ *         The \p key_type is valid for use with the \p algorithm
  * \retval #PSA_ERROR_INVALID_ARGUMENT
- *         \p algorithm is not a valid, specific MAC algorithm recognized and
- *         supported by this core, or \p key_type describes a key which is
- *         inconsistent with the specified \p algorithm.
- * \retval #PSA_ERROR_INVALID_ARGUMENT
- *         \p algorithm tries to truncate the MAC to a size which would be
- *         larger than the underlying algorithm's maximum output length.
+ *         The \p key_type is not valid for use with the \p algorithm
  */
-MBEDTLS_STATIC_TESTABLE psa_status_t psa_get_mac_output_length(
+MBEDTLS_STATIC_TESTABLE psa_status_t psa_mac_key_can_do(
     psa_algorithm_t algorithm,
-    psa_key_type_t key_type,
-    size_t *length )
+    psa_key_type_t key_type )
 {
-    /* Get the default length for the algorithm and key combination. None of the
-     * currently supported algorithms have a default output length dependent on
-     * key size, so setting it to a bogus value is OK. */
-    size_t default_length = PSA_MAC_LENGTH( key_type, 0,
-                                            PSA_ALG_FULL_LENGTH_MAC( algorithm ) );
-
-    /* PSA_MAC_LENGTH, when called on a full-length algorithm identifier, can
-     * currently return either 0 (unknown algorithm) or 1 (cipher-MAC with
-     * stream cipher) in cases where the key type / algorithm combination would
-     * be invalid. */
-    if( default_length == 0 || default_length == 1 )
-        return( PSA_ERROR_INVALID_ARGUMENT );
-
-    /* Output the expected (potentially truncated) length as long as it can
-     * actually be output by the algorithm. Truncation length of '0' means
-     * default output length of the keytype-algorithm combination. */
-    if( PSA_MAC_TRUNCATED_LENGTH( algorithm ) == 0 )
-    {
-        *length = default_length;
-        return( PSA_SUCCESS );
+    if( PSA_ALG_IS_HMAC( algorithm ) ) {
+        if( key_type == PSA_KEY_TYPE_HMAC )
+            return( PSA_SUCCESS );
     }
-    else if( PSA_MAC_TRUNCATED_LENGTH( algorithm ) <= default_length )
+
+    if( PSA_ALG_IS_BLOCK_CIPHER_MAC( algorithm ) )
     {
-        *length = PSA_MAC_TRUNCATED_LENGTH( algorithm );
-        return( PSA_SUCCESS );
+        /* Check that we're calling PSA_BLOCK_CIPHER_BLOCK_LENGTH with a cipher
+         * key. */
+        if( ( key_type & PSA_KEY_TYPE_CATEGORY_MASK ) ==
+            PSA_KEY_TYPE_CATEGORY_SYMMETRIC )
+        {
+            /* PSA_BLOCK_CIPHER_BLOCK_LENGTH returns 1 for stream ciphers and
+             * the block length (larger than 1) for block ciphers. */
+            if( PSA_BLOCK_CIPHER_BLOCK_LENGTH( key_type ) > 1 )
+                return( PSA_SUCCESS );
+        }
     }
-    else
-        return( PSA_ERROR_INVALID_ARGUMENT );
+
+    return( PSA_ERROR_INVALID_ARGUMENT );
 }
 
 /** Try to allocate a buffer to an empty key slot.
@@ -765,28 +751,19 @@
         ( PSA_ALG_FULL_LENGTH_MAC( alg1 ) ==
           PSA_ALG_FULL_LENGTH_MAC( alg2 ) ) )
     {
-        /* Calculate the actual requested output length for both sides. In case
-         * of at-least-this-length wildcard algorithms, the requested output
-         * length is the shortest allowed length. */
-        size_t alg1_len = 0;
-        size_t alg2_len = 0;
-        if( PSA_SUCCESS != psa_get_mac_output_length(
-                                PSA_ALG_TRUNCATED_MAC( alg1,
-                                    PSA_MAC_TRUNCATED_LENGTH( alg1 ) ),
-                                key_type,
-                                &alg1_len ) )
-        {
+        /* Validate the combination of key type and algorithm. Since the base
+         * algorithm of alg1 and alg2 are the same, we only need this once. */
+        if( PSA_SUCCESS != psa_mac_key_can_do( alg1, key_type ) )
             return( 0 );
-        }
-        if( PSA_SUCCESS != psa_get_mac_output_length(
-                                PSA_ALG_TRUNCATED_MAC( alg2,
-                                    PSA_MAC_TRUNCATED_LENGTH( alg2 ) ),
-                                key_type,
-                                &alg2_len ) )
-        {
-            return( 0 );
-        }
 
+        /* Get the output length for the algorithm and key combination. None of
+         * the currently supported algorithms have an output length dependent on
+         * actual key size, so setting it to a bogus value is currently OK.
+         * Note that for at-least-this-length wildcard algorithms, the output
+         * length is set to the shortest allowed length, which allows us to
+         * calculate the most restrictive tag length for the intersection. */
+        size_t alg1_len = PSA_MAC_LENGTH( key_type, 0, alg1 );
+        size_t alg2_len = PSA_MAC_LENGTH( key_type, 0, alg2 );
         size_t max_len = alg1_len > alg2_len ? alg1_len : alg2_len;
 
         /* If both are wildcards, return most restrictive wildcard */
@@ -810,9 +787,10 @@
             else
                 return( 0 );
         }
-        /* If none of them are wildcards, check whether we can match
-         * default-length with exact-length, and return exact-length in that
-         * case. */
+        /* If none of them are wildcards, check whether this is a case of one
+         * specifying the default length and the other a specific length. If the
+         * specific length equals the default length for this key type, the
+         * intersection would be the specific-length algorithm. */
         if( alg1_len == alg2_len )
             return( PSA_ALG_TRUNCATED_MAC( alg1, alg1_len ) );
     }
@@ -853,27 +831,25 @@
         ( PSA_ALG_FULL_LENGTH_MAC( policy_alg ) ==
           PSA_ALG_FULL_LENGTH_MAC( requested_alg ) ) )
     {
-        size_t actual_output_length;
-        size_t default_output_length;
-        if( PSA_SUCCESS != psa_get_mac_output_length(
-                            requested_alg,
-                            key_type,
-                            &actual_output_length ) )
-        {
+        /* Validate the combination of key type and algorithm. Since the policy
+         * and requested algorithms are the same, we only need this once. */
+        if( PSA_SUCCESS != psa_mac_key_can_do( policy_alg, key_type ) )
             return( 0 );
-        }
-        if( PSA_SUCCESS != psa_get_mac_output_length(
-                            PSA_ALG_FULL_LENGTH_MAC( requested_alg ),
-                            key_type,
-                            &default_output_length ) )
-        {
-            return( 0 );
-        }
+
+        /* Get both the requested and the default output length for this
+         * algorithm and key combination. None of the currently supported
+         * algorithms have an output length dependent on actual key size, so
+         * setting it to a bogus value is currently OK. */
+        size_t requested_output_length = PSA_MAC_LENGTH(
+                                            key_type, 0, requested_alg );
+        size_t default_output_length = PSA_MAC_LENGTH(
+                                        key_type, 0,
+                                        PSA_ALG_FULL_LENGTH_MAC( requested_alg ) );
 
         /* If the policy is default-length, only allow an algorithm with
          * a declared exact-length matching the default. */
         if( PSA_MAC_TRUNCATED_LENGTH( policy_alg ) == 0 )
-            return( actual_output_length == default_output_length );
+            return( requested_output_length == default_output_length );
 
         /* If the requested algorithm is default-length, allow it if the policy
          * is exactly the default length. */
@@ -889,7 +865,7 @@
         if( ( policy_alg & PSA_ALG_MAC_AT_LEAST_THIS_LENGTH_FLAG ) != 0 )
         {
             return( PSA_MAC_TRUNCATED_LENGTH( policy_alg ) <=
-                    actual_output_length );
+                    requested_output_length );
         }
     }
     /* If policy_alg is a generic key agreement operation, then using it for
@@ -2981,7 +2957,6 @@
     psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
     psa_status_t unlock_status = PSA_ERROR_CORRUPTION_DETECTED;
     psa_key_slot_t *slot;
-    size_t output_length = 0;
     psa_key_usage_t usage =
         is_sign ? PSA_KEY_USAGE_SIGN_HASH : PSA_KEY_USAGE_VERIFY_HASH;
 
@@ -3002,12 +2977,15 @@
     if( status != PSA_SUCCESS )
         goto exit;
 
-    status = psa_get_mac_output_length( alg, slot->attr.type,
-                                        &output_length );
+    /* Validate the combination of key type and algorithm */
+    status = psa_mac_key_can_do( alg, slot->attr.type );
     if( status != PSA_SUCCESS )
         goto exit;
 
-    operation->mac_size = (uint8_t) output_length;
+    /* Get the output length for the algorithm and key combination. None of the
+     * currently supported algorithms have an output length dependent on actual
+     * key size, so setting it to a bogus value is currently OK. */
+    operation->mac_size = PSA_MAC_LENGTH( slot->attr.type, 0, alg );
 
     if( operation->mac_size < 4 )
     {
@@ -3019,6 +2997,15 @@
         goto exit;
     }
 
+    if( operation->mac_size >
+        PSA_MAC_LENGTH( slot->attr.type, 0, PSA_ALG_FULL_LENGTH_MAC( alg ) ) )
+    {
+        /* It's impossible to "truncate" to a larger length than the full length
+         * of the algorithm. */
+        status = PSA_ERROR_INVALID_ARGUMENT;
+        goto exit;
+    }
+
 #if defined(MBEDTLS_CMAC_C)
     if( PSA_ALG_FULL_LENGTH_MAC( alg ) == PSA_ALG_CMAC )
     {
diff --git a/library/psa_crypto_invasive.h b/library/psa_crypto_invasive.h
index 99156de..1e5a407 100644
--- a/library/psa_crypto_invasive.h
+++ b/library/psa_crypto_invasive.h
@@ -78,9 +78,9 @@
 #endif /* !defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) */
 
 #if defined(MBEDTLS_TEST_HOOKS) && defined(MBEDTLS_PSA_CRYPTO_C)
-psa_status_t psa_get_mac_output_length( psa_algorithm_t algorithm,
-                                        psa_key_type_t key_type,
-                                        size_t *length );
+psa_status_t psa_mac_key_can_do(
+    psa_algorithm_t algorithm,
+    psa_key_type_t key_type );
 #endif /* MBEDTLS_TEST_HOOKS && MBEDTLS_PSA_CRYPTO_C */
 
 #endif /* PSA_CRYPTO_INVASIVE_H */
diff --git a/tests/suites/test_suite_psa_crypto.data b/tests/suites/test_suite_psa_crypto.data
index 84267ad..3824fa0 100644
--- a/tests/suites/test_suite_psa_crypto.data
+++ b/tests/suites/test_suite_psa_crypto.data
@@ -1086,7 +1086,8 @@
 mac_setup:PSA_KEY_TYPE_AES:"000102030405060708090a0b0c0d0e0f":PSA_ALG_CMAC:PSA_SUCCESS
 
 PSA MAC setup: bad algorithm (HMAC without specified hash)
-mac_setup:PSA_KEY_TYPE_HMAC:"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f":PSA_ALG_HMAC(0):PSA_ERROR_INVALID_ARGUMENT
+# Either INVALID_ARGUMENT or NOT_SUPPORTED would be reasonable here
+mac_setup:PSA_KEY_TYPE_HMAC:"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f":PSA_ALG_HMAC(0):PSA_ERROR_NOT_SUPPORTED
 
 PSA MAC setup: bad algorithm (unsupported HMAC hash algorithm)
 depends_on:!PSA_WANT_ALG_MD2
diff --git a/tests/suites/test_suite_psa_crypto_metadata.function b/tests/suites/test_suite_psa_crypto_metadata.function
index 5839611..4bf5635 100644
--- a/tests/suites/test_suite_psa_crypto_metadata.function
+++ b/tests/suites/test_suite_psa_crypto_metadata.function
@@ -160,9 +160,7 @@
     TEST_EQUAL( length, PSA_MAC_LENGTH( key_type, key_bits, alg ) );
 
 #if defined(MBEDTLS_TEST_HOOKS) && defined(MBEDTLS_PSA_CRYPTO_C)
-    size_t output_length = 0;
-    PSA_ASSERT( psa_get_mac_output_length( alg, key_type, &output_length ) );
-    TEST_EQUAL( length, output_length );
+    PSA_ASSERT( psa_mac_key_can_do( alg, key_type ) );
 #endif
 
 exit: ;