diff --git a/scripts/mbedtls_dev/crypto_knowledge.py b/scripts/mbedtls_dev/crypto_knowledge.py index 55eb54d59..1491a844a 100644 --- a/scripts/mbedtls_dev/crypto_knowledge.py +++ b/scripts/mbedtls_dev/crypto_knowledge.py @@ -213,7 +213,7 @@ class KeyType: This function does not currently handle key derivation or PAKE. """ - #pylint: disable=too-many-return-statements + #pylint: disable=too-many-branches,too-many-return-statements if alg.is_wildcard: return False if alg.is_invalid_truncation(): @@ -425,28 +425,41 @@ class Algorithm: """ return short_expression(self.expression, level=level) + HASH_LENGTH = { + 'PSA_ALG_MD5': 16, + 'PSA_ALG_SHA_1': 20, + } + HASH_LENGTH_BITS_RE = re.compile(r'([0-9]+)\Z') + @classmethod + def hash_length(cls, alg: str) -> int: + """The length of the given hash algorithm, in bytes.""" + if alg in cls.HASH_LENGTH: + return cls.HASH_LENGTH[alg] + m = cls.HASH_LENGTH_BITS_RE.search(alg) + if m: + return int(m.group(1)) // 8 + raise ValueError('Unknown hash length for ' + alg) + PERMITTED_TAG_LENGTHS = { 'PSA_ALG_CCM': frozenset([4, 6, 8, 10, 12, 14, 16]), 'PSA_ALG_CHACHA20_POLY1305': frozenset([16]), 'PSA_ALG_GCM': frozenset([4, 8, 12, 13, 14, 15, 16]), } MAC_LENGTH = { - 'PSA_ALG_CBC_MAC': 16, - 'PSA_ALG_CMAC': 16, - 'PSA_ALG_HMAC(PSA_ALG_MD5)': 16, - 'PSA_ALG_HMAC(PSA_ALG_SHA_1)': 20, + 'PSA_ALG_CBC_MAC': 16, # actually the block cipher length + 'PSA_ALG_CMAC': 16, # actually the block cipher length } - HMAC_WITH_NOMINAL_LENGTH_RE = re.compile(r'PSA_ALG_HMAC\(\w+([0-9])+\)\Z') + HMAC_RE = re.compile(r'PSA_ALG_HMAC\((.*)\)\Z') @classmethod - def mac_or_tag_length(cls, base: str) -> FrozenSet[int]: + def mac_or_tag_lengths(cls, base: str) -> FrozenSet[int]: """Return the set of permitted lengths for the given MAC or AEAD tag.""" if base in cls.PERMITTED_TAG_LENGTHS: return cls.PERMITTED_TAG_LENGTHS[base] max_length = cls.MAC_LENGTH.get(base, None) if max_length is None: - m = cls.HMAC_WITH_NOMINAL_LENGTH_RE.match(base) + m = cls.HMAC_RE.match(base) if m: - max_length = int(m.group(1)) // 8 + max_length = cls.hash_length(m.group(1)) if max_length is None: raise ValueError('Unknown permitted lengths for ' + base) return frozenset(range(4, max_length + 1)) @@ -466,7 +479,7 @@ class Algorithm: if m: base = m.group('base') to_length = int(m.group('length'), 0) - permitted_lengths = self.mac_or_tag_length(base) + permitted_lengths = self.mac_or_tag_lengths(base) if to_length not in permitted_lengths: return True return False diff --git a/tests/scripts/generate_psa_tests.py b/tests/scripts/generate_psa_tests.py index 27ee52d6b..6fd311e63 100755 --- a/tests/scripts/generate_psa_tests.py +++ b/tests/scripts/generate_psa_tests.py @@ -518,6 +518,32 @@ class StorageFormat: self.version = version #type: int self.forward = forward #type: bool + RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z') + @classmethod + def valid_key_size_for_algorithm( + cls, + key_type: psa_storage.Expr, bits: int, + alg: psa_storage.Expr + ) -> bool: + """Whether the given key type and size are valid for the algorithm. + + Normally only the type and algorithm matter for compatibility, and + this is handled in crypto_knowledge.KeyType.can_do(). This function + exists to detect exceptional cases. Exceptional cases detected here + are not tested in OpFail and should therefore have manually written + test cases. + """ + #pylint: disable=unused-argument + # OAEP requires room for two hashes plus wrapping + m = cls.RSA_OAEP_RE.match(alg.string) + if m: + hash_alg = m.group(1) + hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg) + key_length = (bits + 7) // 8 + # Leave enough room for at least one byte of plaintext + return key_length > 2 * hash_length + 2 + return True + def make_test_case(self, key: StorageTestData) -> test_case.TestCase: """Construct a storage format test case for the given key. @@ -546,7 +572,8 @@ class StorageFormat: # encodings of the attributes. # Raw data keys have no useful exercise anyway so there is no # loss of test coverage. - if key.type.string != 'PSA_KEY_TYPE_RAW_DATA': + if key.type.string != 'PSA_KEY_TYPE_RAW_DATA' and \ + self.valid_key_size_for_algorithm(key.type, key.bits, key.alg): flags.append('TEST_FLAG_EXERCISE') if 'READ_ONLY' in key.lifetime.string: flags.append('TEST_FLAG_READ_ONLY')