1
0
mirror of https://github.com/Mbed-TLS/mbedtls.git synced 2025-08-07 06:42:56 +03:00

Merge remote-tracking branch 'development' into psa_crypto_config-in-full

Conflicts:
* `include/psa/crypto_sizes.h`: the addition of the `u` suffix in this branch
  conflicts with the rework of the calculation of `PSA_HASH_MAX_SIZE` and
  `PSA_HMAC_MAX_HASH_BLOCK_SIZE` in `development`. Use the new definitions
  from `development`, and add the `u` suffix to the relevant constants.
This commit is contained in:
Gilles Peskine
2023-08-30 18:32:57 +02:00
121 changed files with 3699 additions and 1307 deletions

View File

@@ -192,6 +192,10 @@ pre_initialize_variables () {
# default to -O2, use -Ox _after_ this if you want another level
ASAN_CFLAGS='-O2 -Werror -fsanitize=address,undefined -fno-sanitize-recover=all'
# Platform tests have an allocation that returns null
export ASAN_OPTIONS="allocator_may_return_null=1"
export MSAN_OPTIONS="allocator_may_return_null=1"
# Gather the list of available components. These are the functions
# defined in this script whose name starts with "component_".
ALL_COMPONENTS=$(compgen -A function component_ | sed 's/component_//')
@@ -1872,6 +1876,16 @@ skip_suites_without_constant_flow () {
export SKIP_TEST_SUITES
}
skip_all_except_given_suite () {
# Skip all but the given test suite
SKIP_TEST_SUITES=$(
ls -1 tests/suites/test_suite_*.function |
grep -v $1.function |
sed 's/tests.suites.test_suite_//; s/\.function$//' |
tr '\n' ,)
export SKIP_TEST_SUITES
}
component_test_memsan_constant_flow () {
# This tests both (1) accesses to undefined memory, and (2) branches or
# memory access depending on secret values. To distinguish between those:
@@ -1931,6 +1945,16 @@ component_test_valgrind_constant_flow () {
# details are left in Testing/<date>/DynamicAnalysis.xml
msg "test: some suites (full minus MBEDTLS_USE_PSA_CRYPTO, valgrind + constant flow)"
make memcheck
# Test asm path in constant time module - by default, it will test the plain C
# path under Valgrind or Memsan. Running only the constant_time tests is fast (<1s)
msg "test: valgrind asm constant_time"
scripts/config.py --force set MBEDTLS_TEST_CONSTANT_FLOW_ASM
skip_all_except_given_suite test_suite_constant_time
cmake -D CMAKE_BUILD_TYPE:String=Release .
make clean
make
make memcheck
}
component_test_valgrind_constant_flow_psa () {
@@ -3895,7 +3919,7 @@ support_test_aesni() {
# We can only grep /proc/cpuinfo on Linux, so this also checks for Linux
(gcc -v 2>&1 | grep Target | grep -q x86_64) &&
[[ "$HOSTTYPE" == "x86_64" && "$OSTYPE" == "linux-gnu" ]] &&
(grep '^flags' /proc/cpuinfo | grep -qw aes)
(lscpu | grep -qw aes)
}
component_test_aesni () { # ~ 60s
@@ -3908,29 +3932,136 @@ component_test_aesni () { # ~ 60s
msg "build: default config with different AES implementations"
scripts/config.py set MBEDTLS_AESNI_C
scripts/config.py unset MBEDTLS_AES_USE_HARDWARE_ONLY
scripts/config.py set MBEDTLS_HAVE_ASM
# test the intrinsics implementation
msg "AES tests, test intrinsics"
make clean
make test programs/test/selftest CC=gcc CFLAGS='-Werror -Wall -Wextra -mpclmul -msse2 -maes'
make CC=gcc CFLAGS='-Werror -Wall -Wextra -mpclmul -msse2 -maes'
# check that we built intrinsics - this should be used by default when supported by the compiler
./programs/test/selftest | grep "AESNI code" | grep -q "intrinsics"
./programs/test/selftest aes | grep "AESNI code" | grep -q "intrinsics"
# test the asm implementation
msg "AES tests, test assembly"
make clean
make test programs/test/selftest CC=gcc CFLAGS='-Werror -Wall -Wextra -mno-pclmul -mno-sse2 -mno-aes'
make CC=gcc CFLAGS='-Werror -Wall -Wextra -mno-pclmul -mno-sse2 -mno-aes'
# check that we built assembly - this should be built if the compiler does not support intrinsics
./programs/test/selftest | grep "AESNI code" | grep -q "assembly"
./programs/test/selftest aes | grep "AESNI code" | grep -q "assembly"
# test the plain C implementation
scripts/config.py unset MBEDTLS_AESNI_C
scripts/config.py unset MBEDTLS_AES_USE_HARDWARE_ONLY
msg "AES tests, plain C"
make clean
make test programs/test/selftest CC=gcc CFLAGS='-O2 -Werror'
make CC=gcc CFLAGS='-O2 -Werror'
# check that there is no AESNI code present
./programs/test/selftest | not grep -q "AESNI code"
./programs/test/selftest aes | not grep -q "AESNI code"
not grep -q "AES note: using AESNI" ./programs/test/selftest
grep -q "AES note: built-in implementation." ./programs/test/selftest
# test the intrinsics implementation
scripts/config.py set MBEDTLS_AESNI_C
scripts/config.py set MBEDTLS_AES_USE_HARDWARE_ONLY
msg "AES tests, test AESNI only"
make clean
make CC=gcc CFLAGS='-Werror -Wall -Wextra -mpclmul -msse2 -maes'
./programs/test/selftest aes | grep -q "AES note: using AESNI"
./programs/test/selftest aes | not grep -q "AES note: built-in implementation."
grep -q "AES note: using AESNI" ./programs/test/selftest
not grep -q "AES note: built-in implementation." ./programs/test/selftest
}
support_test_aesni_m32() {
support_test_m32_o0 && (lscpu | grep -qw aes)
}
component_test_aesni_m32 () { # ~ 60s
# This tests are duplicated from component_test_aesni for i386 target
#
# AESNI intrinsic code supports i386 and assembly code does not support it.
msg "build: default config with different AES implementations"
scripts/config.py set MBEDTLS_AESNI_C
scripts/config.py set MBEDTLS_PADLOCK_C
scripts/config.py unset MBEDTLS_AES_USE_HARDWARE_ONLY
scripts/config.py set MBEDTLS_HAVE_ASM
# test the intrinsics implementation
msg "AES tests, test intrinsics"
make clean
make CC=gcc CFLAGS='-m32 -Werror -Wall -Wextra -mpclmul -msse2 -maes' LDFLAGS='-m32'
# check that we built intrinsics - this should be used by default when supported by the compiler
./programs/test/selftest aes | grep "AESNI code" | grep -q "intrinsics"
grep -q "AES note: using AESNI" ./programs/test/selftest
grep -q "AES note: built-in implementation." ./programs/test/selftest
grep -q "AES note: using VIA Padlock" ./programs/test/selftest
grep -q mbedtls_aesni_has_support ./programs/test/selftest
scripts/config.py set MBEDTLS_AESNI_C
scripts/config.py unset MBEDTLS_PADLOCK_C
scripts/config.py set MBEDTLS_AES_USE_HARDWARE_ONLY
msg "AES tests, test AESNI only"
make clean
make CC=gcc CFLAGS='-m32 -Werror -Wall -Wextra -mpclmul -msse2 -maes' LDFLAGS='-m32'
./programs/test/selftest aes | grep -q "AES note: using AESNI"
./programs/test/selftest aes | not grep -q "AES note: built-in implementation."
grep -q "AES note: using AESNI" ./programs/test/selftest
not grep -q "AES note: built-in implementation." ./programs/test/selftest
not grep -q "AES note: using VIA Padlock" ./programs/test/selftest
not grep -q mbedtls_aesni_has_support ./programs/test/selftest
}
# For timebeing, no aarch64 gcc available in CI and no arm64 CI node.
component_build_aes_aesce_armcc () {
msg "Build: AESCE test on arm64 platform without plain C."
scripts/config.py baremetal
# armc[56] don't support SHA-512 intrinsics
scripts/config.py unset MBEDTLS_SHA512_USE_A64_CRYPTO_IF_PRESENT
# Stop armclang warning about feature detection for A64_CRYPTO.
# With this enabled, the library does build correctly under armclang,
# but in baremetal builds (as tested here), feature detection is
# unavailable, and the user is notified via a #warning. So enabling
# this feature would prevent us from building with -Werror on
# armclang. Tracked in #7198.
scripts/config.py unset MBEDTLS_SHA256_USE_A64_CRYPTO_IF_PRESENT
scripts/config.py set MBEDTLS_HAVE_ASM
msg "AESCE, build with default configuration."
scripts/config.py set MBEDTLS_AESCE_C
scripts/config.py unset MBEDTLS_AES_USE_HARDWARE_ONLY
armc6_build_test "-O1 --target=aarch64-arm-none-eabi -march=armv8-a+crypto"
msg "AESCE, build AESCE only"
scripts/config.py set MBEDTLS_AESCE_C
scripts/config.py set MBEDTLS_AES_USE_HARDWARE_ONLY
armc6_build_test "-O1 --target=aarch64-arm-none-eabi -march=armv8-a+crypto"
}
# For timebeing, no VIA Padlock platform available.
component_build_aes_via_padlock () {
msg "AES:VIA PadLock, build with default configuration."
scripts/config.py unset MBEDTLS_AESNI_C
scripts/config.py set MBEDTLS_PADLOCK_C
scripts/config.py unset MBEDTLS_AES_USE_HARDWARE_ONLY
make CC=gcc CFLAGS="$ASAN_CFLAGS -m32 -O2" LDFLAGS="-m32 $ASAN_CFLAGS"
grep -q mbedtls_padlock_has_support ./programs/test/selftest
}
support_build_aes_via_padlock_only () {
( [ "$MBEDTLS_TEST_PLATFORM" == "Linux-x86_64" ] || \
[ "$MBEDTLS_TEST_PLATFORM" == "Linux-amd64" ] ) && \
[ "`dpkg --print-foreign-architectures`" == "i386" ]
}
support_build_aes_aesce_armcc () {
support_build_armcc
}
component_test_aes_only_128_bit_keys () {
@@ -4198,6 +4329,7 @@ component_test_m32_o0 () {
# build) and not the i386-specific inline assembly.
msg "build: i386, make, gcc -O0 (ASan build)" # ~ 30s
scripts/config.py full
scripts/config.py unset MBEDTLS_AESNI_C # AESNI depends on cpu modifiers
make CC=gcc CFLAGS="$ASAN_CFLAGS -m32 -O0" LDFLAGS="-m32 $ASAN_CFLAGS"
msg "test: i386, make, gcc -O0 (ASan build)"
@@ -4215,6 +4347,7 @@ component_test_m32_o2 () {
# and go faster for tests.
msg "build: i386, make, gcc -O2 (ASan build)" # ~ 30s
scripts/config.py full
scripts/config.py unset MBEDTLS_AESNI_C # AESNI depends on cpu modifiers
make CC=gcc CFLAGS="$ASAN_CFLAGS -m32 -O2" LDFLAGS="-m32 $ASAN_CFLAGS"
msg "test: i386, make, gcc -O2 (ASan build)"
@@ -4230,6 +4363,7 @@ support_test_m32_o2 () {
component_test_m32_everest () {
msg "build: i386, Everest ECDH context (ASan build)" # ~ 6 min
scripts/config.py set MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED
scripts/config.py unset MBEDTLS_AESNI_C # AESNI depends on cpu modifiers
make CC=gcc CFLAGS="$ASAN_CFLAGS -m32 -O2" LDFLAGS="-m32 $ASAN_CFLAGS"
msg "test: i386, Everest ECDH context - main suites (inc. selftests) (ASan build)" # ~ 50s
@@ -4683,6 +4817,7 @@ component_test_tls13_only_record_size_limit () {
component_build_mingw () {
msg "build: Windows cross build - mingw64, make (Link Library)" # ~ 30s
scripts/config.py unset MBEDTLS_AESNI_C # AESNI depends on cpu modifiers
make CC=i686-w64-mingw32-gcc AR=i686-w64-mingw32-ar LD=i686-w64-minggw32-ld CFLAGS='-Werror -Wall -Wextra' WINDOWS_BUILD=1 lib programs
# note Make tests only builds the tests, but doesn't run them
@@ -4987,6 +5122,7 @@ component_check_test_helpers () {
python3 -m unittest tests/scripts/translate_ciphers.py 2>&1
}
################################################################
#### Termination
################################################################

View File

@@ -73,15 +73,22 @@ def execute_reference_driver_tests(ref_component, driver_component, outcome_file
Results.log("Error: failed to run reference/driver components")
sys.exit(ret_val)
def analyze_coverage(results, outcomes):
def analyze_coverage(results, outcomes, allow_list, full_coverage):
"""Check that all available test cases are executed at least once."""
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:
# Make this a warning, not an error, as long as we haven't
# fixed this branch to have full coverage of test cases.
results.warning('Test case not executed: {}', key)
if hits == 0 and key not in allow_list:
if full_coverage:
results.error('Test case not executed: {}', key)
else:
results.warning('Test case not executed: {}', key)
elif hits != 0 and key in allow_list:
# Test Case should be removed from the allow list.
if full_coverage:
results.error('Allow listed test case was executed: {}', key)
else:
results.warning('Allow listed test case was executed: {}', key)
def analyze_driver_vs_reference(outcomes, component_ref, component_driver,
ignored_suites, ignored_test=None):
@@ -122,10 +129,11 @@ def analyze_driver_vs_reference(outcomes, component_ref, component_driver,
result = False
return result
def analyze_outcomes(outcomes):
def analyze_outcomes(outcomes, args):
"""Run all analyses on the given outcome collection."""
results = Results()
analyze_coverage(results, outcomes)
analyze_coverage(results, outcomes, args['allow_list'],
args['full_coverage'])
return results
def read_outcome_file(outcome_file):
@@ -151,10 +159,9 @@ by a semicolon.
def do_analyze_coverage(outcome_file, args):
"""Perform coverage analysis."""
del args # unused
outcomes = read_outcome_file(outcome_file)
Results.log("\n*** Analyze coverage ***\n")
results = analyze_outcomes(outcomes)
results = analyze_outcomes(outcomes, args)
return results.error_count == 0
def do_analyze_driver_vs_reference(outcome_file, args):
@@ -175,8 +182,16 @@ def do_analyze_driver_vs_reference(outcome_file, args):
TASKS = {
'analyze_coverage': {
'test_function': do_analyze_coverage,
'args': {}
},
'args': {
'allow_list': [
# Algorithm not supported yet
'test_suite_psa_crypto_metadata;Asymmetric signature: pure EdDSA',
# Algorithm not supported yet
'test_suite_psa_crypto_metadata;Cipher: XTS',
],
'full_coverage': False,
}
},
# There are 2 options to use analyze_driver_vs_reference_xxx locally:
# 1. Run tests and then analysis:
# - tests/scripts/all.sh --outcome-file "$PWD/out.csv" <component_ref> <component_driver>
@@ -426,6 +441,11 @@ def main():
'comma/space-separated list of tasks. ')
parser.add_argument('--list', action='store_true',
help='List all available tasks and exit.')
parser.add_argument('--require-full-coverage', action='store_true',
dest='full_coverage', help="Require all available "
"test cases to be executed and issue an error "
"otherwise. This flag is ignored if 'task' is "
"neither 'all' nor 'analyze_coverage'")
options = parser.parse_args()
if options.list:
@@ -445,6 +465,9 @@ def main():
Results.log('Error: invalid task: {}'.format(task))
sys.exit(1)
TASKS['analyze_coverage']['args']['full_coverage'] = \
options.full_coverage
for task in TASKS:
if task in tasks:
if not TASKS[task]['test_function'](options.outcomes, TASKS[task]['args']):

View File

@@ -24,7 +24,6 @@ from tests/data_files/ and tests/suites/*.data files by default.
"""
import os
import sys
import re
import typing
import argparse
@@ -43,6 +42,7 @@ from generate_test_code import FileWrapper
import scripts_path # pylint: disable=unused-import
from mbedtls_dev import build_tree
from mbedtls_dev import logging_util
def check_cryptography_version():
match = re.match(r'^[0-9]+', cryptography.__version__)
@@ -393,38 +393,6 @@ def list_all(audit_data: AuditData):
loc))
def configure_logger(logger: logging.Logger) -> None:
"""
Configure the logging.Logger instance so that:
- Format is set to "[%(levelname)s]: %(message)s".
- loglevel >= WARNING are printed to stderr.
- loglevel < WARNING are printed to stdout.
"""
class MaxLevelFilter(logging.Filter):
# pylint: disable=too-few-public-methods
def __init__(self, max_level, name=''):
super().__init__(name)
self.max_level = max_level
def filter(self, record: logging.LogRecord) -> bool:
return record.levelno <= self.max_level
log_formatter = logging.Formatter("[%(levelname)s]: %(message)s")
# set loglevel >= WARNING to be printed to stderr
stderr_hdlr = logging.StreamHandler(sys.stderr)
stderr_hdlr.setLevel(logging.WARNING)
stderr_hdlr.setFormatter(log_formatter)
# set loglevel <= INFO to be printed to stdout
stdout_hdlr = logging.StreamHandler(sys.stdout)
stdout_hdlr.addFilter(MaxLevelFilter(logging.INFO))
stdout_hdlr.setFormatter(log_formatter)
logger.addHandler(stderr_hdlr)
logger.addHandler(stdout_hdlr)
def main():
"""
Perform argument parsing.
@@ -457,7 +425,7 @@ def main():
# start main routine
# setup logger
logger = logging.getLogger()
configure_logger(logger)
logging_util.configure_logger(logger)
logger.setLevel(logging.DEBUG if args.verbose else logging.ERROR)
td_auditor = TestDataAuditor(logger)

View File

@@ -26,151 +26,15 @@ import sys
from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional
import scripts_path # pylint: disable=unused-import
from mbedtls_dev import crypto_data_tests
from mbedtls_dev import crypto_knowledge
from mbedtls_dev import macro_collector
from mbedtls_dev import macro_collector #pylint: disable=unused-import
from mbedtls_dev import psa_information
from mbedtls_dev import psa_storage
from mbedtls_dev import test_case
from mbedtls_dev import test_data_generation
def psa_want_symbol(name: str) -> str:
"""Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
if name.startswith('PSA_'):
return name[:4] + 'WANT_' + name[4:]
else:
raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
def finish_family_dependency(dep: str, bits: int) -> str:
"""Finish dep if it's a family dependency symbol prefix.
A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
qualified by the key size. If dep is such a symbol, finish it by adjusting
the prefix and appending the key size. Other symbols are left unchanged.
"""
return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
"""Finish any family dependency symbol prefixes.
Apply `finish_family_dependency` to each element of `dependencies`.
"""
return [finish_family_dependency(dep, bits) for dep in dependencies]
SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
'PSA_ALG_ANY_HASH', # only in policies
'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
'PSA_ALG_KEY_AGREEMENT', # chaining
'PSA_ALG_TRUNCATED_MAC', # modifier
])
def automatic_dependencies(*expressions: str) -> List[str]:
"""Infer dependencies of a test case by looking for PSA_xxx symbols.
The arguments are strings which should be C expressions. Do not use
string literals or comments as this function is not smart enough to
skip them.
"""
used = set()
for expr in expressions:
used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
return sorted(psa_want_symbol(name) for name in used)
# Define set of regular expressions and dependencies to optionally append
# extra dependencies for test case.
AES_128BIT_ONLY_DEP_REGEX = r'AES\s(192|256)'
AES_128BIT_ONLY_DEP = ["!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH"]
DEPENDENCY_FROM_KEY = {
AES_128BIT_ONLY_DEP_REGEX: AES_128BIT_ONLY_DEP
}#type: Dict[str, List[str]]
def generate_key_dependencies(description: str) -> List[str]:
"""Return additional dependencies based on pairs of REGEX and dependencies.
"""
deps = []
for regex, dep in DEPENDENCY_FROM_KEY.items():
if re.search(regex, description):
deps += dep
return deps
# A temporary hack: at the time of writing, not all dependency symbols
# are implemented yet. Skip test cases for which the dependency symbols are
# not available. Once all dependency symbols are available, this hack must
# be removed so that a bug in the dependency symbols properly leads to a test
# failure.
def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
return frozenset(symbol
for line in open(filename)
for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
global _implemented_dependencies #pylint: disable=global-statement,invalid-name
if _implemented_dependencies is None:
_implemented_dependencies = \
read_implemented_dependencies('include/psa/crypto_config.h')
if not all((dep.lstrip('!') in _implemented_dependencies or
not dep.lstrip('!').startswith('PSA_WANT'))
for dep in dependencies):
dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
def tweak_key_pair_dependency(dep: str, usage: str):
"""
This helper function add the proper suffix to PSA_WANT_KEY_TYPE_xxx_KEY_PAIR
symbols according to the required usage.
"""
ret_list = list()
if dep.endswith('KEY_PAIR'):
if usage == "BASIC":
# BASIC automatically includes IMPORT and EXPORT for test purposes (see
# config_psa.h).
ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_BASIC', dep))
ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_IMPORT', dep))
ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_EXPORT', dep))
elif usage == "GENERATE":
ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_GENERATE', dep))
else:
# No replacement to do in this case
ret_list.append(dep)
return ret_list
def fix_key_pair_dependencies(dep_list: List[str], usage: str):
new_list = [new_deps
for dep in dep_list
for new_deps in tweak_key_pair_dependency(dep, usage)]
return new_list
class Information:
"""Gather information about PSA constructors."""
def __init__(self) -> None:
self.constructors = self.read_psa_interface()
@staticmethod
def remove_unwanted_macros(
constructors: macro_collector.PSAMacroEnumerator
) -> None:
# Mbed TLS does not support finite-field DSA.
# Don't attempt to generate any related test case.
constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
"""Return the list of known key types, algorithms, etc."""
constructors = macro_collector.InputsForTest()
header_file_names = ['include/psa/crypto_values.h',
'include/psa/crypto_extra.h']
test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
for header_file_name in header_file_names:
constructors.parse_header(header_file_name)
for test_cases in test_suites:
constructors.parse_test_cases(test_cases)
self.remove_unwanted_macros(constructors)
constructors.gather_arguments()
return constructors
def test_case_for_key_type_not_supported(
verb: str, key_type: str, bits: int,
@@ -181,7 +45,7 @@ def test_case_for_key_type_not_supported(
"""Return one test case exercising a key creation method
for an unsupported key type or size.
"""
hack_dependencies_not_implemented(dependencies)
psa_information.hack_dependencies_not_implemented(dependencies)
tc = test_case.TestCase()
short_key_type = crypto_knowledge.short_expression(key_type)
adverb = 'not' if dependencies else 'never'
@@ -197,7 +61,7 @@ def test_case_for_key_type_not_supported(
class KeyTypeNotSupported:
"""Generate test cases for when a key type is not supported."""
def __init__(self, info: Information) -> None:
def __init__(self, info: psa_information.Information) -> None:
self.constructors = info.constructors
ALWAYS_SUPPORTED = frozenset([
@@ -224,20 +88,22 @@ class KeyTypeNotSupported:
# They would be skipped in all configurations, which is noise.
return
import_dependencies = [('!' if param is None else '') +
psa_want_symbol(kt.name)]
psa_information.psa_want_symbol(kt.name)]
if kt.params is not None:
import_dependencies += [('!' if param == i else '') +
psa_want_symbol(sym)
psa_information.psa_want_symbol(sym)
for i, sym in enumerate(kt.params)]
if kt.name.endswith('_PUBLIC_KEY'):
generate_dependencies = []
else:
generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
import_dependencies = fix_key_pair_dependencies(import_dependencies, 'BASIC')
generate_dependencies = \
psa_information.fix_key_pair_dependencies(import_dependencies, 'GENERATE')
import_dependencies = \
psa_information.fix_key_pair_dependencies(import_dependencies, 'BASIC')
for bits in kt.sizes_to_test():
yield test_case_for_key_type_not_supported(
'import', kt.expression, bits,
finish_family_dependencies(import_dependencies, bits),
psa_information.finish_family_dependencies(import_dependencies, bits),
test_case.hex_string(kt.key_material(bits)),
param_descr=param_descr,
)
@@ -251,7 +117,7 @@ class KeyTypeNotSupported:
if not kt.is_public():
yield test_case_for_key_type_not_supported(
'generate', kt.expression, bits,
finish_family_dependencies(generate_dependencies, bits),
psa_information.finish_family_dependencies(generate_dependencies, bits),
str(bits),
param_descr=param_descr,
)
@@ -294,7 +160,7 @@ def test_case_for_key_generation(
) -> test_case.TestCase:
"""Return one test case exercising a key generation.
"""
hack_dependencies_not_implemented(dependencies)
psa_information.hack_dependencies_not_implemented(dependencies)
tc = test_case.TestCase()
short_key_type = crypto_knowledge.short_expression(key_type)
tc.set_description('PSA {} {}-bit'
@@ -308,7 +174,7 @@ def test_case_for_key_generation(
class KeyGenerate:
"""Generate positive and negative (invalid argument) test cases for key generation."""
def __init__(self, info: Information) -> None:
def __init__(self, info: psa_information.Information) -> None:
self.constructors = info.constructors
ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
@@ -327,9 +193,9 @@ class KeyGenerate:
"""
result = 'PSA_SUCCESS'
import_dependencies = [psa_want_symbol(kt.name)]
import_dependencies = [psa_information.psa_want_symbol(kt.name)]
if kt.params is not None:
import_dependencies += [psa_want_symbol(sym)
import_dependencies += [psa_information.psa_want_symbol(sym)
for i, sym in enumerate(kt.params)]
if kt.name.endswith('_PUBLIC_KEY'):
# The library checks whether the key type is a public key generically,
@@ -338,7 +204,8 @@ class KeyGenerate:
generate_dependencies = []
result = 'PSA_ERROR_INVALID_ARGUMENT'
else:
generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
generate_dependencies = \
psa_information.fix_key_pair_dependencies(import_dependencies, 'GENERATE')
for bits in kt.sizes_to_test():
if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
size_dependency = "PSA_VENDOR_RSA_GENERATE_MIN_KEY_BITS <= " + str(bits)
@@ -347,7 +214,7 @@ class KeyGenerate:
test_dependencies = generate_dependencies
yield test_case_for_key_generation(
kt.expression, bits,
finish_family_dependencies(test_dependencies, bits),
psa_information.finish_family_dependencies(test_dependencies, bits),
str(bits),
result
)
@@ -380,7 +247,7 @@ class OpFail:
INCOMPATIBLE = 2
PUBLIC = 3
def __init__(self, info: Information) -> None:
def __init__(self, info: psa_information.Information) -> None:
self.constructors = info.constructors
key_type_expressions = self.constructors.generate_expressions(
sorted(self.constructors.key_types)
@@ -417,8 +284,8 @@ class OpFail:
pretty_alg,
pretty_reason,
' with ' + pretty_type if pretty_type else ''))
dependencies = automatic_dependencies(alg.base_expression, key_type)
dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
dependencies = psa_information.automatic_dependencies(alg.base_expression, key_type)
dependencies = psa_information.fix_key_pair_dependencies(dependencies, 'BASIC')
for i, dep in enumerate(dependencies):
if dep in not_deps:
dependencies[i] = '!' + dep
@@ -445,7 +312,7 @@ class OpFail:
"""Generate failure test cases for keyless operations with the specified algorithm."""
if alg.can_do(category):
# Compatible operation, unsupported algorithm
for dep in automatic_dependencies(alg.base_expression):
for dep in psa_information.automatic_dependencies(alg.base_expression):
yield self.make_test_case(alg, category,
self.Reason.NOT_SUPPORTED,
not_deps=frozenset([dep]))
@@ -463,7 +330,7 @@ class OpFail:
key_is_compatible = kt.can_do(alg)
if key_is_compatible and alg.can_do(category):
# Compatible key and operation, unsupported algorithm
for dep in automatic_dependencies(alg.base_expression):
for dep in psa_information.automatic_dependencies(alg.base_expression):
yield self.make_test_case(alg, category,
self.Reason.NOT_SUPPORTED,
kt=kt, not_deps=frozenset([dep]))
@@ -569,7 +436,7 @@ class StorageTestData(StorageKey):
class StorageFormat:
"""Storage format stability test cases."""
def __init__(self, info: Information, version: int, forward: bool) -> None:
def __init__(self, info: psa_information.Information, version: int, forward: bool) -> None:
"""Prepare to generate test cases for storage format stability.
* `info`: information about the API. See the `Information` class.
@@ -636,13 +503,13 @@ class StorageFormat:
verb = 'save' if self.forward else 'read'
tc = test_case.TestCase()
tc.set_description(verb + ' ' + key.description)
dependencies = automatic_dependencies(
dependencies = psa_information.automatic_dependencies(
key.lifetime.string, key.type.string,
key.alg.string, key.alg2.string,
)
dependencies = finish_family_dependencies(dependencies, key.bits)
dependencies += generate_key_dependencies(key.description)
dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
dependencies = psa_information.finish_family_dependencies(dependencies, key.bits)
dependencies += psa_information.generate_key_dependencies(key.description)
dependencies = psa_information.fix_key_pair_dependencies(dependencies, 'BASIC')
tc.set_dependencies(dependencies)
tc.set_function('key_storage_' + verb)
if self.forward:
@@ -847,13 +714,13 @@ class StorageFormat:
class StorageFormatForward(StorageFormat):
"""Storage format stability test cases for forward compatibility."""
def __init__(self, info: Information, version: int) -> None:
def __init__(self, info: psa_information.Information, version: int) -> None:
super().__init__(info, version, True)
class StorageFormatV0(StorageFormat):
"""Storage format stability test cases for version 0 compatibility."""
def __init__(self, info: Information) -> None:
def __init__(self, info: psa_information.Information) -> None:
super().__init__(info, 0, False)
def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
@@ -963,6 +830,7 @@ class StorageFormatV0(StorageFormat):
yield from super().generate_all_keys()
yield from self.all_keys_for_implicit_usage()
class PSATestGenerator(test_data_generation.TestGenerator):
"""Test generator subclass including PSA targets and info."""
# Note that targets whose names contain 'test_format' have their content
@@ -972,20 +840,23 @@ class PSATestGenerator(test_data_generation.TestGenerator):
lambda info: KeyGenerate(info).test_cases_for_key_generation(),
'test_suite_psa_crypto_not_supported.generated':
lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
'test_suite_psa_crypto_low_hash.generated':
lambda info: crypto_data_tests.HashPSALowLevel(info).all_test_cases(),
'test_suite_psa_crypto_op_fail.generated':
lambda info: OpFail(info).all_test_cases(),
'test_suite_psa_crypto_storage_format.current':
lambda info: StorageFormatForward(info, 0).all_test_cases(),
'test_suite_psa_crypto_storage_format.v0':
lambda info: StorageFormatV0(info).all_test_cases(),
} #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
} #type: Dict[str, Callable[[psa_information.Information], Iterable[test_case.TestCase]]]
def __init__(self, options):
super().__init__(options)
self.info = Information()
self.info = psa_information.Information()
def generate_target(self, name: str, *target_args) -> None:
super().generate_target(name, self.info)
if __name__ == '__main__':
test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)