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:
@@ -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
|
||||
################################################################
|
||||
|
@@ -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']):
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user