Add support for minimum-tag-length AEAD and MAC policies

Includes tests.

Signed-off-by: Steven Cooreman <steven.cooreman@silabs.com>

# Conflicts:
#	include/psa/crypto_values.h
#	tests/suites/test_suite_psa_crypto.function
diff --git a/library/psa_crypto.c b/library/psa_crypto.c
index 8a51e99..b6e26b3 100644
--- a/library/psa_crypto.c
+++ b/library/psa_crypto.c
@@ -663,16 +663,79 @@
     /* Common case: both sides actually specify the same policy. */
     if( alg1 == alg2 )
         return( alg1 );
-    /* If the policies are from the same hash-and-sign family, check
-     * if one is a wildcard. If so the other has the specific algorithm. */
-    if( PSA_ALG_IS_HASH_AND_SIGN( alg1 ) &&
-        PSA_ALG_IS_HASH_AND_SIGN( alg2 ) &&
-        ( alg1 & ~PSA_ALG_HASH_MASK ) == ( alg2 & ~PSA_ALG_HASH_MASK ) )
+    /* Special case: one or both sides contain a wildcard. */
+    if( PSA_ALG_IS_WILDCARD( alg1 ) || PSA_ALG_IS_WILDCARD( alg2 ) )
     {
-        if( PSA_ALG_SIGN_GET_HASH( alg1 ) == PSA_ALG_ANY_HASH )
-            return( alg2 );
-        if( PSA_ALG_SIGN_GET_HASH( alg2 ) == PSA_ALG_ANY_HASH )
-            return( alg1 );
+        /* If the policies are from the same hash-and-sign family, check
+         * if one is a wildcard. If so the other has the specific algorithm. */
+        if( PSA_ALG_IS_HASH_AND_SIGN( alg1 ) &&
+            PSA_ALG_IS_HASH_AND_SIGN( alg2 ) &&
+            ( alg1 & ~PSA_ALG_HASH_MASK ) == ( alg2 & ~PSA_ALG_HASH_MASK ) )
+        {
+            if( PSA_ALG_SIGN_GET_HASH( alg1 ) == PSA_ALG_ANY_HASH )
+                return( alg2 );
+            if( PSA_ALG_SIGN_GET_HASH( alg2 ) == PSA_ALG_ANY_HASH )
+                return( alg1 );
+        }
+        /* If the policies are from the same AEAD family, then at least one
+         * of them is a minimum-tag-length policy. Calculate the most
+         * restrictive tag length. */
+        if( PSA_ALG_IS_AEAD( alg1 ) && PSA_ALG_IS_AEAD( alg2 ) &&
+            ( PSA_ALG_AEAD_WITH_SHORTENED_TAG( alg1, 0 ) ==
+              PSA_ALG_AEAD_WITH_SHORTENED_TAG( alg2, 0 ) ) )
+        {
+            size_t alg1_len = PSA_ALG_AEAD_GET_TAG_LENGTH( alg1 );
+            size_t alg2_len = PSA_ALG_AEAD_GET_TAG_LENGTH( alg2 );
+            size_t max_len = alg1_len > alg2_len ? alg1_len : alg2_len;
+
+            /* If both are wildcards, return most restricitve wildcard */
+            if( PSA_ALG_IS_WILDCARD( alg1 ) && PSA_ALG_IS_WILDCARD( alg2 ) )
+            {
+                return( PSA_ALG_AEAD_WITH_MINIMUM_LENGTH_TAG( alg1, max_len ) );
+            }
+            /* If only one is a wildcard, return specific algorithm if compatible. */
+            if( PSA_ALG_IS_WILDCARD( alg1 ) && (alg1_len <= alg2_len) )
+            {
+                return( alg2 );
+            }
+            if( PSA_ALG_IS_WILDCARD( alg2 ) && (alg2_len <= alg1_len) )
+            {
+                return( alg1 );
+            }
+        }
+        /* If the policies are from the same MAC family, then at least one
+         * of them is a minimum-tag-length policy. Calculate the most
+         * restrictive tag length. */
+        if( PSA_ALG_IS_MAC( alg1 ) && PSA_ALG_IS_MAC( alg2 ) &&
+            ( PSA_ALG_FULL_LENGTH_MAC( alg1 ) ==
+              PSA_ALG_FULL_LENGTH_MAC( alg2 ) ) )
+        {
+            size_t alg1_len = PSA_MAC_TRUNCATED_LENGTH( alg1 );
+            size_t alg2_len = PSA_MAC_TRUNCATED_LENGTH( alg2 );
+            size_t max_len = alg1_len > alg2_len ? alg1_len : alg2_len;
+
+            /* If both are wildcards, return most restricitve wildcard */
+            if( PSA_ALG_IS_WILDCARD( alg1 ) && PSA_ALG_IS_WILDCARD( alg2 ) )
+            {
+                return( PSA_ALG_MAC_WITH_MINIMUM_LENGTH_TAG( alg1, max_len ) );
+            }
+            /* If only one is a wildcard, return specific algorithm if compatible.
+             * Special case: specific MAC algorithm with '0' as length means full-
+             * length MAC, which is always allowed by a wildcard with the same
+             * base algorithm. */
+            if( PSA_ALG_IS_WILDCARD( alg1 ) &&
+                ( ( alg1_len <= alg2_len) ||
+                  ( alg2 == PSA_ALG_FULL_LENGTH_MAC( alg1 ) ) ) )
+            {
+                return( alg2 );
+            }
+            if( PSA_ALG_IS_WILDCARD( alg2 ) &&
+                ( (alg2_len <= alg1_len) ||
+                  ( alg1 == PSA_ALG_FULL_LENGTH_MAC( alg2 ) ) ) )
+            {
+                return( alg1 );
+            }
+        }
     }
     /* If the policies are incompatible, allow nothing. */
     return( 0 );
@@ -681,17 +744,51 @@
 static int psa_key_algorithm_permits( psa_algorithm_t policy_alg,
                                       psa_algorithm_t requested_alg )
 {
+    /* A requested algorithm cannot be a wildcard. */
+    if( PSA_ALG_IS_WILDCARD( requested_alg ) )
+        return( 0 );
     /* Common case: the policy only allows requested_alg. */
     if( requested_alg == policy_alg )
         return( 1 );
-    /* If policy_alg is a hash-and-sign with a wildcard for the hash,
-     * and requested_alg is the same hash-and-sign family with any hash,
-     * then requested_alg is compliant with policy_alg. */
-    if( PSA_ALG_IS_HASH_AND_SIGN( requested_alg ) &&
-        PSA_ALG_SIGN_GET_HASH( policy_alg ) == PSA_ALG_ANY_HASH )
+    /* Special cases: the policy is an algorithm collection. */
+    if( PSA_ALG_IS_WILDCARD( policy_alg ) )
     {
-        return( ( policy_alg & ~PSA_ALG_HASH_MASK ) ==
-                ( requested_alg & ~PSA_ALG_HASH_MASK ) );
+        /* If policy_alg is a hash-and-sign with a wildcard for the hash,
+         * and requested_alg is the same hash-and-sign family with any hash,
+         * then requested_alg is compliant with policy_alg. */
+        if( PSA_ALG_IS_HASH_AND_SIGN( requested_alg ) &&
+            PSA_ALG_SIGN_GET_HASH( policy_alg ) == PSA_ALG_ANY_HASH )
+        {
+            return( ( policy_alg & ~PSA_ALG_HASH_MASK ) ==
+                    ( requested_alg & ~PSA_ALG_HASH_MASK ) );
+        }
+        /* If policy_alg is a wildcard AEAD algorithm of the same base as
+         * the requested algorithm, check the requested tag length to be
+         * equal-length or longer than the wildcard-specified length. */
+        if( PSA_ALG_IS_AEAD( policy_alg ) &&
+            PSA_ALG_IS_AEAD( requested_alg ) &&
+            ( PSA_ALG_AEAD_WITH_SHORTENED_TAG( policy_alg, 0 ) ==
+              PSA_ALG_AEAD_WITH_SHORTENED_TAG( requested_alg, 0 ) ) )
+        {
+            return( PSA_ALG_AEAD_GET_TAG_LENGTH( policy_alg ) <=
+                    PSA_ALG_AEAD_GET_TAG_LENGTH( requested_alg ) );
+        }
+        /* If policy_alg is a wildcard MAC algorithm of the same base as
+         * the requested algorithm, check the requested tag length to be
+         * equal-length or longer than the wildcard-specified length. */
+        if( PSA_ALG_IS_MAC( policy_alg ) &&
+            PSA_ALG_IS_MAC( requested_alg ) &&
+            ( PSA_ALG_FULL_LENGTH_MAC( policy_alg ) ==
+              PSA_ALG_FULL_LENGTH_MAC( requested_alg ) ) )
+        {
+            /* Special case: full-length MAC is encoded with 0-length.
+             * A minimum-length policy will always allow a full-length MAC. */
+            if( PSA_ALG_FULL_LENGTH_MAC( requested_alg ) == requested_alg )
+                return( 1 );
+
+            return( PSA_MAC_TRUNCATED_LENGTH( policy_alg ) <=
+                    PSA_MAC_TRUNCATED_LENGTH( requested_alg ) );
+        }
     }
     /* If policy_alg is a generic key agreement operation, then using it for
      * a key derivation with that key agreement should also be allowed. This
@@ -702,7 +799,7 @@
         return( PSA_ALG_KEY_AGREEMENT_GET_BASE( requested_alg ) ==
                 policy_alg );
     }
-    /* If it isn't permitted, it's forbidden. */
+    /* If it isn't explicitly permitted, it's forbidden. */
     return( 0 );
 }