mirror of
https://github.com/espressif/mbedtls.git
synced 2025-07-13 18:20:30 +08:00

For easier maintenance the framework repository is flattened here and added to the forked branch in source format.
201 lines
8.9 KiB
Python
201 lines
8.9 KiB
Python
"""Generate test cases for PSA API calls, with automatic dependencies.
|
|
"""
|
|
|
|
# Copyright The Mbed TLS Contributors
|
|
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
|
|
#
|
|
|
|
import os
|
|
import re
|
|
from typing import FrozenSet, List, Optional, Set
|
|
|
|
from . import build_tree
|
|
from . import psa_information
|
|
from . import test_case
|
|
|
|
|
|
# Skip test cases for which the dependency symbols are not defined.
|
|
# We assume that this means that a required mechanism is not implemented.
|
|
# Note that if we erroneously skip generating test cases for
|
|
# mechanisms that are not implemented, this should be caught
|
|
# by the NOT_SUPPORTED test cases generated by generate_psa_tests.py
|
|
# in test_suite_psa_crypto_not_supported and test_suite_psa_crypto_op_fail:
|
|
# those emit tests with negative dependencies, which will not be skipped here.
|
|
|
|
def read_implemented_dependencies(acc: Set[str], filename: str) -> None:
|
|
with open(filename) as input_stream:
|
|
for line in input_stream:
|
|
for symbol in re.findall(r'\bPSA_WANT_\w+\b', line):
|
|
acc.add(symbol)
|
|
|
|
_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
|
|
|
|
def find_dependencies_not_implemented(dependencies: List[str]) -> List[str]:
|
|
"""List the dependencies that are not implemented."""
|
|
global _implemented_dependencies #pylint: disable=global-statement,invalid-name
|
|
if _implemented_dependencies is None:
|
|
# Temporary, while Mbed TLS does not just rely on the TF-PSA-Crypto
|
|
# build system to build its crypto library. When it does, the first
|
|
# case can just be removed.
|
|
|
|
if build_tree.looks_like_root('.'):
|
|
if build_tree.looks_like_mbedtls_root('.') and \
|
|
(not build_tree.is_mbedtls_3_6()):
|
|
include_dir = 'tf-psa-crypto/include'
|
|
else:
|
|
include_dir = 'include'
|
|
|
|
acc = set() #type: Set[str]
|
|
for filename in [
|
|
os.path.join(include_dir, 'psa/crypto_config.h'),
|
|
os.path.join(include_dir, 'psa/crypto_adjust_config_synonyms.h'),
|
|
]:
|
|
read_implemented_dependencies(acc, filename)
|
|
_implemented_dependencies = frozenset(acc)
|
|
return [dep
|
|
for dep in dependencies
|
|
if (dep not in _implemented_dependencies and
|
|
dep.startswith('PSA_WANT'))]
|
|
|
|
|
|
class TestCase(test_case.TestCase):
|
|
"""A PSA test case with automatically inferred dependencies.
|
|
|
|
For mechanisms like ECC curves where the support status includes
|
|
the key bit-size, this class assumes that only one bit-size is
|
|
involved in a given test case.
|
|
"""
|
|
|
|
def __init__(self, dependency_prefix: Optional[str] = None) -> None:
|
|
"""Construct a test case for a PSA Crypto API call.
|
|
|
|
`dependency_prefix`: prefix to use in dependencies. Defaults to
|
|
``'PSA_WANT_'``. Use ``'MBEDTLS_PSA_BUILTIN_'``
|
|
when specifically testing builtin implementations.
|
|
"""
|
|
super().__init__()
|
|
del self.dependencies
|
|
self.manual_dependencies = [] #type: List[str]
|
|
self.automatic_dependencies = set() #type: Set[str]
|
|
self.dependency_prefix = dependency_prefix #type: Optional[str]
|
|
self.negated_dependencies = set() #type: Set[str]
|
|
self.key_bits = None #type: Optional[int]
|
|
self.key_pair_usage = None #type: Optional[List[str]]
|
|
|
|
def set_key_bits(self, key_bits: Optional[int]) -> None:
|
|
"""Use the given key size for automatic dependency generation.
|
|
|
|
Call this function before set_arguments() if relevant.
|
|
|
|
This is only relevant for ECC and DH keys. For other key types,
|
|
this information is ignored.
|
|
"""
|
|
self.key_bits = key_bits
|
|
|
|
def set_key_pair_usage(self, key_pair_usage: Optional[List[str]]) -> None:
|
|
"""Use the given suffixes for key pair dependencies.
|
|
|
|
Call this function before set_arguments() if relevant.
|
|
|
|
This is only relevant for key pair types. For other key types,
|
|
this information is ignored.
|
|
"""
|
|
self.key_pair_usage = key_pair_usage
|
|
|
|
def infer_dependencies(self, arguments: List[str]) -> List[str]:
|
|
"""Infer dependencies based on the test case arguments."""
|
|
dependencies = psa_information.automatic_dependencies(*arguments,
|
|
prefix=self.dependency_prefix)
|
|
if self.key_bits is not None:
|
|
dependencies = psa_information.finish_family_dependencies(dependencies,
|
|
self.key_bits)
|
|
if self.key_pair_usage is not None:
|
|
dependencies = psa_information.fix_key_pair_dependencies(dependencies,
|
|
self.key_pair_usage)
|
|
if 'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE' in dependencies and \
|
|
'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE' not in self.negated_dependencies and \
|
|
self.key_bits is not None:
|
|
size_dependency = ('PSA_VENDOR_RSA_GENERATE_MIN_KEY_BITS <= ' +
|
|
str(self.key_bits))
|
|
dependencies.append(size_dependency)
|
|
return dependencies
|
|
|
|
def assumes_not_supported(self, name: str) -> None:
|
|
"""Negate the given mechanism for automatic dependency generation.
|
|
|
|
`name` can be either a dependency symbol (``PSA_WANT_xxx``) or
|
|
a mechanism name (``PSA_KEY_TYPE_xxx``, etc.).
|
|
|
|
Call this function before set_arguments() for a test case that should
|
|
run if the given mechanism is not supported.
|
|
|
|
Call modifiers such as set_key_bits() and set_key_pair_usage() before
|
|
calling this method, if applicable.
|
|
|
|
A mechanism is a PSA_XXX symbol, e.g. PSA_KEY_TYPE_AES, PSA_ALG_HMAC,
|
|
etc. For mechanisms like ECC curves where the support status includes
|
|
the key bit-size, this class assumes that only one bit-size is
|
|
involved in a given test case.
|
|
"""
|
|
if name.startswith('PSA_WANT_'):
|
|
self.negated_dependencies.add(name)
|
|
return
|
|
if name == 'PSA_KEY_TYPE_RSA_KEY_PAIR' and \
|
|
self.key_bits is not None and \
|
|
self.key_pair_usage == ['GENERATE']:
|
|
# When RSA key pair generation is not supported, it could be
|
|
# due to the specific key size is out of range, or because
|
|
# RSA key pair generation itself is not supported. Assume the
|
|
# latter.
|
|
dep = psa_information.psa_want_symbol(name, prefix=self.dependency_prefix)
|
|
|
|
self.negated_dependencies.add(dep + '_GENERATE')
|
|
return
|
|
dependencies = self.infer_dependencies([name])
|
|
# * If we have more than one dependency to negate, the result would
|
|
# say that all of the dependencies are disabled, which is not
|
|
# a desirable outcome: the negation of (A and B) is (!A or !B),
|
|
# not (!A and !B).
|
|
# * If we have no dependency to negate, the result wouldn't be a
|
|
# not-supported case.
|
|
# Assert that we don't reach either such case.
|
|
assert len(dependencies) == 1
|
|
self.negated_dependencies.add(dependencies[0])
|
|
|
|
def set_arguments(self, arguments: List[str]) -> None:
|
|
"""Set test case arguments and automatically infer dependencies."""
|
|
super().set_arguments(arguments)
|
|
dependencies = self.infer_dependencies(arguments)
|
|
for i in range(len(dependencies)): #pylint: disable=consider-using-enumerate
|
|
if dependencies[i] in self.negated_dependencies:
|
|
dependencies[i] = '!' + dependencies[i]
|
|
self.skip_if_any_not_implemented(dependencies)
|
|
self.automatic_dependencies.update(dependencies)
|
|
|
|
def set_dependencies(self, dependencies: List[str]) -> None:
|
|
"""Override any previously added automatic or manual dependencies.
|
|
|
|
Also override any previous instruction to skip the test case.
|
|
"""
|
|
self.manual_dependencies = dependencies
|
|
self.automatic_dependencies.clear()
|
|
self.skip_reasons = []
|
|
|
|
def add_dependencies(self, dependencies: List[str]) -> None:
|
|
"""Add manual dependencies."""
|
|
self.manual_dependencies += dependencies
|
|
|
|
def get_dependencies(self) -> List[str]:
|
|
# Make the output independent of the order in which the dependencies
|
|
# are calculated by the script. Also avoid duplicates. This makes
|
|
# the output robust with respect to refactoring of the scripts.
|
|
dependencies = set(self.manual_dependencies)
|
|
dependencies.update(self.automatic_dependencies)
|
|
return sorted(dependencies)
|
|
|
|
def skip_if_any_not_implemented(self, dependencies: List[str]) -> None:
|
|
"""Skip the test case if any of the given dependencies is not implemented."""
|
|
not_implemented = find_dependencies_not_implemented(dependencies)
|
|
for dep in not_implemented:
|
|
self.skip_because('not implemented: ' + dep)
|