diff --git a/scripts/all-helpers.sh b/scripts/all-helpers.sh index 9135dafc7..f63143e30 100644 --- a/scripts/all-helpers.sh +++ b/scripts/all-helpers.sh @@ -67,15 +67,15 @@ helper_libtestdriver1_adjust_config() { scripts/config.py "$base_config" fi - # Enable PSA-based config (necessary to use drivers) - # MBEDTLS_PSA_CRYPTO_CONFIG is a legacy setting which should only be set on 3.6 LTS branches. if in_mbedtls_repo && in_3_6_branch; then + # Enable PSA-based config (necessary to use drivers) + # MBEDTLS_PSA_CRYPTO_CONFIG is a legacy setting which should only be set on 3.6 LTS branches. scripts/config.py set MBEDTLS_PSA_CRYPTO_CONFIG - fi - # Dynamic secure element support is a deprecated feature and needs to be disabled here. - # This is done to have the same form of psa_key_attributes_s for libdriver and library. - scripts/config.py unset MBEDTLS_PSA_CRYPTO_SE_C + # Dynamic secure element support is a deprecated feature and needs to be disabled here. + # This is done to have the same form of psa_key_attributes_s for libdriver and library. + scripts/config.py unset MBEDTLS_PSA_CRYPTO_SE_C + fi # If threading is enabled on the normal build, then we need to enable it in the drivers as well, # otherwise we will end up running multithreaded tests without mutexes to protect them. @@ -139,12 +139,13 @@ helper_psasim_config() { scripts/config.py full scripts/config.py unset MBEDTLS_PSA_CRYPTO_C scripts/config.py unset MBEDTLS_PSA_CRYPTO_STORAGE_C - # Dynamic secure element support is a deprecated feature and it is not - # available when CRYPTO_C and PSA_CRYPTO_STORAGE_C are disabled. - scripts/config.py unset MBEDTLS_PSA_CRYPTO_SE_C + if in_mbedtls_repo && in_3_6_branch; then + # Dynamic secure element support is a deprecated feature and it is not + # available when CRYPTO_C and PSA_CRYPTO_STORAGE_C are disabled. + scripts/config.py unset MBEDTLS_PSA_CRYPTO_SE_C + fi # Disable potentially problematic features scripts/config.py unset MBEDTLS_X509_RSASSA_PSS_SUPPORT - scripts/config.py unset MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED scripts/config.py unset MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED scripts/config.py unset MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED scripts/config.py unset MBEDTLS_ECP_RESTARTABLE @@ -152,8 +153,10 @@ helper_psasim_config() { else scripts/config.py crypto_full scripts/config.py unset MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS - # We need to match the client with MBEDTLS_PSA_CRYPTO_SE_C - scripts/config.py unset MBEDTLS_PSA_CRYPTO_SE_C + if in_mbedtls_repo && in_3_6_branch; then + # We need to match the client with MBEDTLS_PSA_CRYPTO_SE_C + scripts/config.py unset MBEDTLS_PSA_CRYPTO_SE_C + fi # Also ensure MBEDTLS_PSA_CRYPTO_KEY_ID_ENCODES_OWNER not set (to match client) scripts/config.py unset MBEDTLS_PSA_CRYPTO_KEY_ID_ENCODES_OWNER fi diff --git a/scripts/apidoc_full.sh b/scripts/apidoc_full.sh index 34daf37b5..902a515c6 100755 --- a/scripts/apidoc_full.sh +++ b/scripts/apidoc_full.sh @@ -12,17 +12,48 @@ set -eu -CONFIG_H='include/mbedtls/mbedtls_config.h' +. $(dirname "$0")/project_detection.sh -if [ -r $CONFIG_H ]; then :; else - echo "$CONFIG_H not found" >&2 - exit 1 +if in_mbedtls_repo; then + CONFIG_H='include/mbedtls/mbedtls_config.h' + if [ -r $CONFIG_H ]; then :; else + echo "$CONFIG_H not found" >&2 + fi + if ! in_3_6_branch; then + CRYPTO_CONFIG_H='tf-psa-crypto/include/psa/crypto_config.h' + fi fi -CONFIG_BAK=${CONFIG_H}.bak -cp -p $CONFIG_H $CONFIG_BAK +if in_tf_psa_crypto_repo; then + CRYPTO_CONFIG_H='include/psa/crypto_config.h' +fi -scripts/config.py realfull -make apidoc +if in_tf_psa_crypto_repo || (in_mbedtls_repo && ! in_3_6_branch); then + if [ -r $CRYPTO_CONFIG_H ]; then :; else + echo "$CRYPTO_CONFIG_H not found" >&2 + exit 1 + fi + CRYPTO_CONFIG_BAK=${CRYPTO_CONFIG_H}.bak + cp -p $CRYPTO_CONFIG_H $CRYPTO_CONFIG_BAK +fi -mv $CONFIG_BAK $CONFIG_H +if in_mbedtls_repo; then + CONFIG_BAK=${CONFIG_H}.bak + cp -p $CONFIG_H $CONFIG_BAK + scripts/config.py realfull + make apidoc + mv $CONFIG_BAK $CONFIG_H +elif in_tf_psa_crypto_repo; then + scripts/config.py realfull + TF_PSA_CRYPTO_ROOT_DIR=$PWD + rm -rf doxygen/build-apidoc-full + mkdir doxygen/build-apidoc-full + cd doxygen/build-apidoc-full + cmake -DCMAKE_BUILD_TYPE:String=Check -DGEN_FILES=ON $TF_PSA_CRYPTO_ROOT_DIR + make tfpsacrypto-apidoc + cd $TF_PSA_CRYPTO_ROOT_DIR +fi + +if in_tf_psa_crypto_repo || (in_mbedtls_repo && ! in_3_6_branch); then + mv $CRYPTO_CONFIG_BAK $CRYPTO_CONFIG_H +fi diff --git a/scripts/check-doxy-blocks.pl b/scripts/check-doxy-blocks.pl index 3199c2ab4..eef307712 100755 --- a/scripts/check-doxy-blocks.pl +++ b/scripts/check-doxy-blocks.pl @@ -16,7 +16,11 @@ use strict; use File::Basename; # C/header files in the following directories will be checked -my @directories = qw(include/mbedtls library doxygen/input); +my @mbedtls_directories = qw(include/mbedtls library doxygen/input); +my @tf_psa_crypto_directories = qw(include/psa include/tf-psa-crypto + include/mbedtls + drivers/builtin/include/mbedtls + drivers/builtin/src core doxygen/input); # very naive pattern to find directives: # everything with a backslach except '\0' and backslash at EOL @@ -53,13 +57,19 @@ sub check_dir { } } +open my $project_file, "scripts/project_name.txt" or die "This script must be run from Mbed TLS or TF-PSA-Crypto root directory"; +my $project = <$project_file>; +chomp($project); +my @directories; + +if ($project eq "TF-PSA-Crypto") { + @directories = @tf_psa_crypto_directories +} elsif ($project eq "Mbed TLS") { + @directories = @mbedtls_directories +} # Check that the script is being run from the project's root directory. for my $dir (@directories) { - if (! -d $dir) { - die "This script must be run from the Mbed TLS root directory"; - } else { - check_dir($dir) - } + check_dir($dir) } exit $exit_code; diff --git a/scripts/check_names.py b/scripts/check_names.py index 0f5427574..77c91c2b5 100755 --- a/scripts/check_names.py +++ b/scripts/check_names.py @@ -701,6 +701,7 @@ class TFPSACryptoCodeParser(CodeParser): all_macros["public"] = self.parse_macros([ "include/psa/*.h", "include/tf-psa-crypto/*.h", + "include/mbedtls/*.h", "drivers/builtin/include/mbedtls/*.h", "drivers/everest/include/everest/everest.h", "drivers/everest/include/everest/x25519.h" @@ -717,6 +718,7 @@ class TFPSACryptoCodeParser(CodeParser): enum_consts = self.parse_enum_consts([ "include/psa/*.h", "include/tf-psa-crypto/*.h", + "include/mbedtls/*.h", "drivers/builtin/include/mbedtls/*.h", "core/*.h", "drivers/builtin/src/*.h", @@ -728,6 +730,7 @@ class TFPSACryptoCodeParser(CodeParser): identifiers, excluded_identifiers = self.parse_identifiers([ "include/psa/*.h", "include/tf-psa-crypto/*.h", + "include/mbedtls/*.h", "drivers/builtin/include/mbedtls/*.h", "core/*.h", "drivers/builtin/src/*.h", @@ -737,6 +740,7 @@ class TFPSACryptoCodeParser(CodeParser): mbed_psa_words = self.parse_mbed_psa_words([ "include/psa/*.h", "include/tf-psa-crypto/*.h", + "include/mbedtls/*.h", "drivers/builtin/include/mbedtls/*.h", "core/*.h", "drivers/builtin/src/*.h", diff --git a/scripts/code_style.py b/scripts/code_style.py index 63cc6dc7a..ef2750819 100755 --- a/scripts/code_style.py +++ b/scripts/code_style.py @@ -41,16 +41,29 @@ def list_generated_files() -> FrozenSet[str]: would conform to the code style, but this would be difficult, especially with respect to the placement of line breaks in long logical lines. """ - # Parse check-generated-files.sh to get an up-to-date list of - # generated files. Read the file rather than calling it so that - # this script only depends on Git, Python and uncrustify, and not other - # tools such as sh or grep which might not be available on Windows. - # This introduces a limitation: check-generated-files.sh must have - # the expected format and must list the files explicitly, not through - # wildcards or command substitution. - content = open(CHECK_GENERATED_FILES, encoding="utf-8").read() - checks = re.findall(CHECK_CALL_RE, content) - return frozenset(word for s in checks for word in s.split()) + if build_tree.is_mbedtls_3_6(): + # Parse check-generated-files.sh to get an up-to-date list of + # generated files. Read the file rather than calling it so that + # this script only depends on Git, Python and uncrustify, and not other + # tools such as sh or grep which might not be available on Windows. + # This introduces a limitation: check-generated-files.sh must have + # the expected format and must list the files explicitly, not through + # wildcards or command substitution. + content = open(CHECK_GENERATED_FILES, encoding="utf-8").read() + checks = re.findall(CHECK_CALL_RE, content) + return frozenset(word for s in checks for word in s.split()) + else: + output = subprocess.check_output(["framework/scripts/make_generated_files.py", + "--list"], universal_newlines=True) + # psa_test_wrappers.[hc], generated by generate_psa_wrappers.py, are + # currently committed and unknown to make_generated_files.py. Add them + # here to the list of generated file as we do not want to check their + # coding style. + if build_tree.looks_like_tf_psa_crypto_root("."): + output += "tests/include/test/psa_test_wrappers.h\n" + output += "tests/src/psa_test_wrappers.c" + + return frozenset(line for line in output.splitlines()) # Check for comment string indicating an auto-generated file AUTOGEN_RE = re.compile(r"Warning[ :-]+This file is (now )?auto[ -]?generated", @@ -72,7 +85,6 @@ def get_src_files(since: Optional[str]) -> List[str]: """ file_patterns = ["*.[hc]", "tests/suites/*.function", - "tf-psa-crypto/tests/suites/*.function", "scripts/data_files/*.fmt"] output = subprocess.check_output(["git", "ls-files"] + file_patterns, universal_newlines=True) @@ -140,8 +152,8 @@ def get_src_files(since: Optional[str]) -> List[str]: is_file_autogenerated(filename))] else: src_files = [filename for filename in src_files - if not (filename.startswith("tf-psa-crypto/drivers/everest/") or - filename.startswith("tf-psa-crypto/drivers/p256-m/") or + if not (filename.startswith("drivers/everest/") or + filename.startswith("drivers/p256-m/") or filename in generated_files or is_file_autogenerated(filename))] return src_files diff --git a/scripts/demo_common.sh b/scripts/demo_common.sh new file mode 100644 index 000000000..8f5c942d0 --- /dev/null +++ b/scripts/demo_common.sh @@ -0,0 +1,181 @@ +## Common shell functions used by demo scripts programs/*/*.sh. + +## How to write a demo script +## ========================== +## +## Include this file near the top of each demo script: +## . "${0%/*}/demo_common.sh" +## +## Start with a "msg" call that explains the purpose of the script. +## Then call the "depends_on" function to ensure that all config +## dependencies are met. +## +## As the last thing in the script, call the cleanup function. +## +## You can use the functions and variables described below. + +set -e -u + +# Check if the provided path ($1) can be a valid root for Mbed TLS or TF-PSA-Crypto. +# This is based on the fact that "scripts/project_name.txt" exists. +is_valid_root () { + if ! [ -f "$1/scripts/project_name.txt" ]; then + return 1; + fi + return 0; +} + +## At the end of the while loop below $root_dir will point to the root directory +## of the Mbed TLS or TF-PSA-Crypto source tree. +root_dir="${0%/*}" +## Find a nice path to the root directory, avoiding unnecessary "../". +## +## The code supports demo scripts nested up to 4 levels deep. +## +## The code works no matter where the demo script is relative to the current +## directory, even if it is called with a relative path. +n=4 +while true; do + # If we went up too many folders, then give up and return a failure. + if [ $n -eq 0 ]; then + echo >&2 "This doesn't seem to be an Mbed TLS source tree." + exit 125 + fi + + if is_valid_root "$root_dir"; then + break; + fi + + n=$((n - 1)) + case $root_dir in + .) root_dir="..";; + ..|?*/..) root_dir="$root_dir/..";; + ?*/*) root_dir="${root_dir%/*}";; + /*) root_dir="/";; + *) root_dir=".";; + esac +done + +# Now that we have a root path we can source the "project_detection.sh" script. +. "$root_dir/framework/scripts/project_detection.sh" + +## msg LINE... +## msg &2 <&2 +. $(dirname "$0")/project_detection.sh + +if in_mbedtls_repo || in_tf_psa_crypto_repo; then :; else + echo "Must be run from Mbed TLS root or TF-PSA-Crypto root" >&2 exit 1 fi @@ -28,5 +30,8 @@ if grep -E "(warning|error):" doc.filtered; then exit 1; fi -make apidoc_clean +if in_mbedtls_repo; then + make apidoc_clean +fi + rm -f doc.out doc.err doc.filtered diff --git a/scripts/generate_config_tests.py b/scripts/generate_config_tests.py index e3c1d8ddc..013fc0680 100755 --- a/scripts/generate_config_tests.py +++ b/scripts/generate_config_tests.py @@ -12,6 +12,7 @@ from typing import Iterable, Iterator, List, Optional, Tuple import project_scripts # pylint: disable=unused-import import config +from mbedtls_framework import build_tree from mbedtls_framework import config_common from mbedtls_framework import test_case from mbedtls_framework import test_data_generation @@ -172,9 +173,10 @@ class ConfigTestGenerator(test_data_generation.TestGenerator): self.targets['test_suite_config.mbedtls_boolean'] = \ lambda: enumerate_boolean_setting_cases(self.mbedtls_config) if 'CryptoConfig' in config_members: - self.psa_config = config.CryptoConfig() - self.targets['test_suite_config.psa_boolean'] = \ - lambda: enumerate_boolean_setting_cases(self.psa_config) + if build_tree.is_mbedtls_3_6(): + self.psa_config = config.CryptoConfig() + self.targets['test_suite_config.psa_boolean'] = \ + lambda: enumerate_boolean_setting_cases(self.psa_config) elif 'TFPSACryptoConfig' in config_members: self.psa_config = config.TFPSACryptoConfig() self.targets['test_suite_config.psa_boolean'] = \ diff --git a/scripts/generate_test_cert_macros.py b/scripts/generate_test_cert_macros.py index b6d97fcd1..3b2154a4d 100755 --- a/scripts/generate_test_cert_macros.py +++ b/scripts/generate_test_cert_macros.py @@ -54,7 +54,7 @@ INPUT_ARGS = [ def main(): parser = argparse.ArgumentParser() - default_output_path = os.path.join(TESTS_DIR, 'src', 'test_certs.h') + default_output_path = os.path.join(TESTS_DIR, 'include', 'test', 'test_certs.h') parser.add_argument('--output', type=str, default=default_output_path) parser.add_argument('--list-dependencies', action='store_true') args = parser.parse_args() diff --git a/scripts/generate_test_keys.py b/scripts/generate_test_keys.py index f5d69019e..76a02e15b 100755 --- a/scripts/generate_test_keys.py +++ b/scripts/generate_test_keys.py @@ -168,7 +168,7 @@ def collect_keys() -> Tuple[str, str]: return ''.join(arrays), '\n'.join(look_up_table) def main() -> None: - default_output_path = guess_project_root() + "/framework/tests/include/test/test_keys.h" + default_output_path = guess_project_root() + "/tests/include/test/test_keys.h" argparser = argparse.ArgumentParser() argparser.add_argument("--output", help="Output file", default=default_output_path) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py new file mode 100755 index 000000000..1e9dbb944 --- /dev/null +++ b/scripts/generate_tls_handshake_tests.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 + +""" +Generate miscellaneous TLS test cases relating to the handshake. +""" + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +import argparse +import os +import sys +from typing import Optional + +from mbedtls_framework import tls_test_case +from mbedtls_framework import typing_util +from mbedtls_framework.tls_test_case import Side, Version +import translate_ciphers + + +# Assume that a TLS 1.2 ClientHello used in these tests will be at most +# this many bytes long. +TLS12_CLIENT_HELLO_ASSUMED_MAX_LENGTH = 255 + +# Minimum handshake fragment length that Mbed TLS supports. +TLS_HANDSHAKE_FRAGMENT_MIN_LENGTH = 4 + +def write_tls_handshake_defragmentation_test( + #pylint: disable=too-many-arguments + out: typing_util.Writable, + side: Side, + length: Optional[int], + version: Optional[Version] = None, + cipher: Optional[str] = None, + etm: Optional[bool] = None, #encrypt-then-mac (only relevant for CBC) + variant: str = '' +) -> None: + """Generate one TLS handshake defragmentation test. + + :param out: file to write to. + :param side: which side is Mbed TLS. + :param length: fragment length, or None to not fragment. + :param version: protocol version, if forced. + """ + #pylint: disable=chained-comparison,too-many-branches,too-many-statements + + our_args = '' + their_args = '' + + if length is None: + description = 'no fragmentation, for reference' + else: + description = 'len=' + str(length) + if version is not None: + description += ', TLS 1.' + str(version.value) + description = f'Handshake defragmentation on {side.name.lower()}: {description}' + tc = tls_test_case.TestCase(description) + + if version is not None: + their_args += ' ' + version.openssl_option() + # Emit a version requirement, because we're forcing the version via + # OpenSSL, not via Mbed TLS, and the automatic depdendencies in + # ssl-opt.sh only handle forcing the version via Mbed TLS. + tc.requirements.append(version.requires_command()) + if side == Side.SERVER and version == Version.TLS12 and \ + length is not None and \ + length <= TLS12_CLIENT_HELLO_ASSUMED_MAX_LENGTH: + # Server-side ClientHello defragmentation is only supported in + # the TLS 1.3 message parser. When that parser sees an 1.2-only + # ClientHello, it forwards the reassembled record to the + # TLS 1.2 ClientHello parser so the ClientHello can be fragmented. + # When TLS 1.3 support is disabled in the server (at compile-time + # or at runtime), the TLS 1.2 ClientHello parser only sees + # the first fragment of the ClientHello. + tc.requirements.append('requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3') + tc.description += ' with 1.3 support' + + # To guarantee that the handhake messages are large enough and need to be + # split into fragments, the tests require certificate authentication. + # The party in control of the fragmentation operations is OpenSSL and + # will always use server5.crt (548 Bytes). + if length is not None and \ + length >= TLS_HANDSHAKE_FRAGMENT_MIN_LENGTH: + tc.requirements.append('requires_certificate_authentication') + if version == Version.TLS12 and side == Side.CLIENT: + #The server uses an ECDSA cert, so make sure we have a compatible key exchange + tc.requirements.append( + 'requires_config_enabled MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED') + else: + # This test case may run in a pure-PSK configuration. OpenSSL doesn't + # allow this by default with TLS 1.3. + their_args += ' -allow_no_dhe_kex' + + if length is None: + forbidden_patterns = [ + 'waiting for more fragments', + ] + wanted_patterns = [] + elif length < TLS_HANDSHAKE_FRAGMENT_MIN_LENGTH: + their_args += ' -split_send_frag ' + str(length) + tc.exit_code = 1 + forbidden_patterns = [] + wanted_patterns = [ + 'handshake message too short: ' + str(length), + 'SSL - An invalid SSL record was received', + ] + if side == Side.SERVER: + wanted_patterns[0:0] = ['<= parse client hello'] + elif version == Version.TLS13: + wanted_patterns[0:0] = ['=> ssl_tls13_process_server_hello'] + else: + their_args += ' -split_send_frag ' + str(length) + forbidden_patterns = [] + wanted_patterns = [ + 'reassembled record', + fr'initial handshake fragment: {length}, 0\.\.{length} of [0-9]\+', + fr'subsequent handshake fragment: [0-9]\+, {length}\.\.', + fr'Prepare: waiting for more handshake fragments {length}/', + fr'Consume: waiting for more handshake fragments {length}/', + ] + + if cipher is not None: + mbedtls_cipher = translate_ciphers.translate_mbedtls(cipher) + if side == Side.CLIENT: + our_args += ' force_ciphersuite=' + mbedtls_cipher + if 'NULL' in cipher: + their_args += ' -cipher ALL@SECLEVEL=0:COMPLEMENTOFALL@SECLEVEL=0' + else: + # For TLS 1.2, when Mbed TLS is the server, we must force the + # cipher suite on the client side, because passing + # force_ciphersuite to ssl_server2 would force a TLS-1.2-only + # server, which does not support a fragmented ClientHello. + tc.requirements.append('requires_ciphersuite_enabled ' + mbedtls_cipher) + their_args += ' -cipher ' + translate_ciphers.translate_ossl(cipher) + if 'NULL' in cipher: + their_args += '@SECLEVEL=0' + + if etm is not None: + if etm: + tc.requirements.append('requires_config_enabled MBEDTLS_SSL_ENCRYPT_THEN_MAC') + our_args += ' etm=' + str(int(etm)) + (wanted_patterns if etm else forbidden_patterns)[0:0] = [ + 'using encrypt then mac', + ] + + tc.description += variant + + if side == Side.CLIENT: + tc.client = '$P_CLI debug_level=4' + our_args + tc.server = '$O_NEXT_SRV' + their_args + tc.wanted_client_patterns = wanted_patterns + tc.forbidden_client_patterns = forbidden_patterns + else: + their_args += ' -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key' + our_args += ' auth_mode=required' + tc.client = '$O_NEXT_CLI' + their_args + tc.server = '$P_SRV debug_level=4' + our_args + tc.wanted_server_patterns = wanted_patterns + tc.forbidden_server_patterns = forbidden_patterns + tc.write(out) + + +CIPHERS_FOR_TLS12_HANDSHAKE_DEFRAGMENTATION = [ + (None, 'default', None), + ('TLS_ECDHE_ECDSA_WITH_NULL_SHA', 'null', None), + ('TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256', 'ChachaPoly', None), + ('TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256', 'GCM', None), + ('TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256', 'CBC, etm=n', False), + ('TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256', 'CBC, etm=y', True), +] + +def write_tls_handshake_defragmentation_tests(out: typing_util.Writable) -> None: + """Generate TLS handshake defragmentation tests.""" + for side in Side.CLIENT, Side.SERVER: + write_tls_handshake_defragmentation_test(out, side, None) + for length in [512, 513, 256, 128, 64, 36, 32, 16, 13, 5, 4, 3]: + write_tls_handshake_defragmentation_test(out, side, length, + Version.TLS13) + if length == 4: + for (cipher_suite, nickname, etm) in \ + CIPHERS_FOR_TLS12_HANDSHAKE_DEFRAGMENTATION: + write_tls_handshake_defragmentation_test( + out, side, length, Version.TLS12, + cipher=cipher_suite, etm=etm, + variant=', '+nickname) + else: + write_tls_handshake_defragmentation_test(out, side, length, + Version.TLS12) + + +def write_handshake_tests(out: typing_util.Writable) -> None: + """Generate handshake tests.""" + out.write(f"""\ +# Miscellaneous tests related to the TLS handshake layer. +# +# Automatically generated by {os.path.basename(sys.argv[0])}. Do not edit! + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +""") + write_tls_handshake_defragmentation_tests(out) + out.write("""\ +# End of automatically generated file. +""") + +def main() -> None: + """Command line entry point.""" + parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('-o', '--output', + default='tests/opt-testcases/handshake-generated.sh', + help='Output file (default: tests/opt-testcases/handshake-generated.sh)') + args = parser.parse_args() + with open(args.output, 'w') as out: + write_handshake_tests(out) + +if __name__ == '__main__': + main() diff --git a/scripts/make_generated_files.py b/scripts/make_generated_files.py new file mode 100755 index 000000000..1ca0f2da7 --- /dev/null +++ b/scripts/make_generated_files.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 + +# make_generated_files.py +# +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +""" +Generate the TF-PSA-Crypto generated files +""" +import argparse +import filecmp +import shutil +import subprocess +import sys + +from pathlib import Path +from typing import List, Optional + +from mbedtls_framework import build_tree + +class GenerationScript: + """ + Representation of a script generating a configuration independent file. + """ + # pylint: disable=too-few-public-methods + def __init__(self, script: Path, files: List[Path], + output_dir_option: Optional[str] = None, + output_file_option: Optional[str] = None): + # Path from the root of Mbed TLS or TF-PSA-Crypto of the generation script + self.script = script + + # Executable to run the script, needed for Windows + if script.suffix == ".py": + self.exe = "python" + elif script.suffix == ".pl": + self.exe = "perl" + + # List of the default paths from the Mbed TLS or TF-PSA-Crypto root of the + # files the script generates. + self.files = files + + # Output directory script argument. Can be an empty string in case it is a + # positional argument. + self.output_dir_option = output_dir_option + + # Output file script argument. Can be an empty string in case it is a + # positional argument. + self.output_file_option = output_file_option + +def get_generation_script_files(generation_script: str): + """ + Get the list of the default paths of the files that a given script + generates. It is assumed that the script supports the "--list" option. + """ + files = [] + if generation_script.endswith(".py"): + cmd = ["python"] + elif generation_script.endswith(".pl"): + cmd = ["perl"] + cmd += [generation_script, "--list"] + + output = subprocess.check_output(cmd, universal_newlines=True) + for line in output.splitlines(): + files.append(Path(line)) + + return files + +if build_tree.looks_like_tf_psa_crypto_root("."): + TF_PSA_CRYPTO_GENERATION_SCRIPTS = [ + GenerationScript( + Path("scripts/generate_driver_wrappers.py"), + [Path("core/psa_crypto_driver_wrappers.h"), + Path("core/psa_crypto_driver_wrappers_no_static.c")], + "", None + ), + GenerationScript( + Path("framework/scripts/generate_test_keys.py"), + [Path("tests/include/test/test_keys.h")], + None, "--output" + ), + GenerationScript( + Path("scripts/generate_psa_constants.py"), + [Path("programs/psa/psa_constant_names_generated.c")], + "", None + ), + GenerationScript( + Path("framework/scripts/generate_bignum_tests.py"), + get_generation_script_files("framework/scripts/generate_bignum_tests.py"), + "--directory", None + ), + GenerationScript( + Path("framework/scripts/generate_config_tests.py"), + get_generation_script_files("framework/scripts/generate_config_tests.py"), + "--directory", None + ), + GenerationScript( + Path("framework/scripts/generate_ecp_tests.py"), + get_generation_script_files("framework/scripts/generate_ecp_tests.py"), + "--directory", None + ), + GenerationScript( + Path("framework/scripts/generate_psa_tests.py"), + get_generation_script_files("framework/scripts/generate_psa_tests.py"), + "--directory", None + ), + ] + + +if build_tree.looks_like_mbedtls_root(".") and not build_tree.is_mbedtls_3_6(): + MBEDTLS_GENERATION_SCRIPTS = [ + GenerationScript( + Path("scripts/generate_errors.pl"), + [Path("library/error.c")], + None, "tf-psa-crypto/drivers/builtin/include/mbedtls \ + include/mbedtls/ \ + scripts/data_files" + ), + GenerationScript( + Path("scripts/generate_features.pl"), + [Path("library/version_features.c")], + None, "include/mbedtls/ scripts/data_files" + ), + GenerationScript( + Path("framework/scripts/generate_ssl_debug_helpers.py"), + [Path("library/ssl_debug_helpers_generated.c")], + "", None + ), + GenerationScript( + Path("framework/scripts/generate_test_keys.py"), + [Path("tests/include/test/test_keys.h")], + None, "--output" + ), + GenerationScript( + Path("framework/scripts/generate_test_cert_macros.py"), + [Path("tests/include/test/test_certs.h")], + None, "--output" + ), + GenerationScript( + Path("scripts/generate_query_config.pl"), + [Path("programs/test/query_config.c")], + None, "include/mbedtls/mbedtls_config.h \ + tf-psa-crypto/include/psa/crypto_config.h \ + scripts/data_files/query_config.fmt" + ), + GenerationScript( + Path("framework/scripts/generate_config_tests.py"), + get_generation_script_files("framework/scripts/generate_config_tests.py"), + "--directory", None + ), + GenerationScript( + Path("framework/scripts/generate_tls13_compat_tests.py"), + [Path("tests/opt-testcases/tls13-compat.sh")], + None, "--output" + ), + GenerationScript( + Path("framework/scripts/generate_tls_handshake_tests.py"), + [Path("tests/opt-testcases/handshake-generated.sh")], + None, "--output" + ), + GenerationScript( + Path("scripts/generate_visualc_files.pl"), + get_generation_script_files("scripts/generate_visualc_files.pl"), + "--directory", None + ), + ] + +def get_generated_files(generation_scripts: List[GenerationScript]): + """ + List the generated files in Mbed TLS or TF-PSA-Crypto. The path from root + is returned for each generated files. + """ + files = [] + for generation_script in generation_scripts: + files += generation_script.files + + return files + +def make_generated_files(generation_scripts: List[GenerationScript]): + """ + Generate the configuration independent files in their default location in + the Mbed TLS or TF-PSA-Crypto tree. + """ + for generation_script in generation_scripts: + subprocess.run([generation_script.exe, str(generation_script.script)], check=True) + +def check_generated_files(generation_scripts: List[GenerationScript], root: Path): + """ + Check that the given root directory contains the generated files as expected/ + generated by this script. + """ + for generation_script in generation_scripts: + for file in generation_script.files: + file = root / file + bak_file = file.with_name(file.name + ".bak") + if bak_file.exists(): + bak_file.unlink() + file.rename(bak_file) + + command = [generation_script.exe, str(generation_script.script)] + if generation_script.output_dir_option is not None: + command += [generation_script.output_dir_option, + str(root / Path(generation_script.files[0].parent))] + elif generation_script.output_file_option is not None: + command += generation_script.output_file_option.split() + command += [str(root / Path(generation_script.files[0]))] + subprocess.run([item for item in command if item.strip()], check=True) + + for file in generation_script.files: + file = root / file + bak_file = file.with_name(file.name + ".bak") + if not filecmp.cmp(file, bak_file): + ref_file = file.with_name(file.name + ".ref") + ref_file = root / ref_file + if ref_file.exists(): + ref_file.unlink() + shutil.copy(file, ref_file) + print(f"Generated file {file} not identical to the reference one {ref_file}.") + file.unlink() + bak_file.rename(file) + +def main(): + """ + Main function of this program + """ + parser = argparse.ArgumentParser() + + parser.add_argument('--list', action='store_true', + default=False, help='List generated files.') + parser.add_argument('--root', metavar='DIR', + help='Root of the tree containing the generated files \ + to check (default: Mbed TLS or TF-PSA-Cryto root.)') + parser.add_argument('--check', action='store_true', + default=False, help='Check the generated files in root') + + args = parser.parse_args() + + if not build_tree.looks_like_root("."): + raise RuntimeError("This script must be run from Mbed TLS or TF-PSA-Crypto root.") + + if build_tree.looks_like_tf_psa_crypto_root("."): + generation_scripts = TF_PSA_CRYPTO_GENERATION_SCRIPTS + elif not build_tree.is_mbedtls_3_6(): + generation_scripts = MBEDTLS_GENERATION_SCRIPTS + else: + raise Exception("No support for Mbed TLS 3.6") + + if args.list: + files = get_generated_files(generation_scripts) + for file in files: + print(str(file)) + elif args.check: + check_generated_files(generation_scripts, Path(args.root or ".")) + else: + make_generated_files(generation_scripts) + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/mbedtls_framework/build_tree.py b/scripts/mbedtls_framework/build_tree.py index 00868f527..cb5f5c958 100644 --- a/scripts/mbedtls_framework/build_tree.py +++ b/scripts/mbedtls_framework/build_tree.py @@ -73,10 +73,10 @@ def check_repo_path(): raise Exception("This script must be run from Mbed TLS root") def chdir_to_root() -> None: - """Detect the root of the Mbed TLS source tree and change to it. + """Detect the root of the Mbed TLS or TF-PSA-Crypto source tree and change to it. - The current directory must be up to two levels deep inside an Mbed TLS - source tree. + The current directory must be up to two levels deep inside an Mbed TLS or + TF-PSA-Crypto source tree. """ for d in [os.path.curdir, os.path.pardir, @@ -84,7 +84,7 @@ def chdir_to_root() -> None: if looks_like_root(d): os.chdir(d) return - raise Exception('Mbed TLS source tree not found') + raise Exception('Mbed TLS or TF-PSA-Crypto source tree not found') def guess_project_root(): """Guess project source code directory. diff --git a/scripts/mbedtls_framework/code_wrapper/psa_wrapper.py b/scripts/mbedtls_framework/code_wrapper/psa_wrapper.py index 0148cde25..af892ba81 100644 --- a/scripts/mbedtls_framework/code_wrapper/psa_wrapper.py +++ b/scripts/mbedtls_framework/code_wrapper/psa_wrapper.py @@ -79,7 +79,7 @@ class PSAWrapper(c_wrapper_generator.Base): self.out_c_f = out_c_f self.out_h_f = out_h_f - self.mbedtls_root = build_tree.guess_mbedtls_root() + self.project_root = build_tree.guess_project_root() self.read_config(config) self.read_headers(in_headers) @@ -99,18 +99,19 @@ class PSAWrapper(c_wrapper_generator.Base): c_parsing_helper.read_function_declarations(self.functions, header_path) def rel_path(self, filename: str, path_list: List[str] = ['include', 'psa']) -> str: - """Return the estimated path in relationship to the mbedtls_root. + """Return the estimated path in relationship to the project_root. The method allows overriding the targetted sub-directory. - Currently the default is set to mbedtls_root/include/psa.""" + Currently the default is set to project_root/include/psa.""" # 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 not build_tree.is_mbedtls_3_6(): + if build_tree.looks_like_mbedtls_root(self.project_root) and \ + not build_tree.is_mbedtls_3_6(): path_list = ['tf-psa-crypto' ] + path_list - return os.path.join(self.mbedtls_root, *path_list, filename) + return os.path.join(self.project_root, *path_list, filename) - return os.path.join(self.mbedtls_root, *path_list, filename) + return os.path.join(self.project_root, *path_list, filename) # Utility Methods @staticmethod diff --git a/scripts/mbedtls_framework/tls_test_case.py b/scripts/mbedtls_framework/tls_test_case.py new file mode 100644 index 000000000..73bb039a8 --- /dev/null +++ b/scripts/mbedtls_framework/tls_test_case.py @@ -0,0 +1,101 @@ +"""Library for constructing an Mbed TLS ssl-opt test case. +""" + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +import enum +import re +from typing import List + +from . import typing_util + + +class TestCase: + """Data about an ssl-opt test case.""" + #pylint: disable=too-few-public-methods + + def __init__(self, description: str) -> None: + # List of shell snippets to call before run_test, typically + # calls to requires_xxx functions. + self.requirements = [] #type: List[str] + # Test case description (first argument to run_test). + self.description = description + # Client command line. + # This will be placed directly inside double quotes in the shell script. + self.client = '$P_CLI' + # Server command line. + # This will be placed directly inside double quotes in the shell script. + self.server = '$P_SRV' + # Expected client exit code. + self.exit_code = 0 + + # Note that all patterns matched in the logs are in BRE + # (Basic Regular Expression) syntax, more precisely in the BRE + # dialect that is the default for GNU grep. The main difference + # with Python regular expressions is that the operators for + # grouping `\(...\)`, alternation `x\|y`, option `x\?`, + # one-or-more `x\+` and repetition ranges `x\{M,N\}` must be + # preceded by a backslash. The characters `()|?+{}` stand for + # themselves. + + # BRE for text that must be present in the client log (run_test -c). + self.wanted_client_patterns = [] #type: List[str] + # BRE for text that must be present in the server log (run_test -s). + self.wanted_server_patterns = [] #type: List[str] + # BRE for text that must not be present in the client log (run_test -C). + self.forbidden_client_patterns = [] #type: List[str] + # BRE for text that must not be present in the server log (run_test -S). + self.forbidden_server_patterns = [] #type: List[str] + + @staticmethod + def _quote(raw: str) -> str: + """Quote the given string for sh. + + Use double quotes, because that's currently the norm in ssl-opt.sh. + """ + return '"' + re.sub(r'([$"\\`])', r'\\\1', raw) + '"' + + def write(self, out: typing_util.Writable) -> None: + """Write the test case to the specified file.""" + for req in self.requirements: + out.write(req + '\n') + out.write(f'run_test {self._quote(self.description)} \\\n') + out.write(f' "{self.server}" \\\n') + out.write(f' "{self.client}" \\\n') + out.write(f' {self.exit_code}') + for pat in self.wanted_server_patterns: + out.write(' \\\n -s ' + self._quote(pat)) + for pat in self.forbidden_server_patterns: + out.write(' \\\n -S ' + self._quote(pat)) + for pat in self.wanted_client_patterns: + out.write(' \\\n -c ' + self._quote(pat)) + for pat in self.forbidden_client_patterns: + out.write(' \\\n -C ' + self._quote(pat)) + out.write('\n\n') + + +class Side(enum.Enum): + CLIENT = 0 + SERVER = 1 + +class Version(enum.Enum): + """TLS protocol version. + + This class doesn't know about DTLS yet. + """ + + TLS12 = 2 + TLS13 = 3 + + def force_version(self) -> str: + """Argument to pass to ssl_client2 or ssl_server2 to force this version.""" + return f'force_version=tls1{self.value}' + + def openssl_option(self) -> str: + """Option to pass to openssl s_client or openssl s_server to select this version.""" + return f'-tls1_{self.value}' + + def requires_command(self) -> str: + """Command to require this protocol version in an ssl-opt.sh test case.""" + return 'requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_' + str(self.value) diff --git a/scripts/project_detection.sh b/scripts/project_detection.sh index bbe28139d..97bdf1a77 100644 --- a/scripts/project_detection.sh +++ b/scripts/project_detection.sh @@ -9,25 +9,79 @@ # help detect which project (Mbed TLS, TF-PSA-Crypto) # or which Mbed TLS branch they are in. -# Project detection +# Project detection. +# +# Both Mbed TLS and TF-PSA-Cryto repos have a "scripts/project_name.txt" file +# which contains the name of the project. They are used in scripts to know in +# which project/folder we're in. +# This function accepts 2 parameters: +# - $1: boolean value which defines the behavior in case +# "scripts/project_name.txt" is not found: +# - 1: exit with error message +# - 0: simply return an error +# - $2: mandatory value which defined the root folder where to look for +# "scripts/project_name.txt". read_project_name_file () { - SCRIPT_DIR=$(pwd) + EXIT_IF_NOT_FOUND=$1 + ROOT_PATH=$2 PROJECT_NAME_FILE="scripts/project_name.txt" - if read -r PROJECT_NAME < "$PROJECT_NAME_FILE"; then :; else - echo "$PROJECT_NAME_FILE does not exist... Exiting..." >&2 - exit 1 + # Check if file exists. + if [ ! -f "$ROOT_PATH/$PROJECT_NAME_FILE" ]; then + if $EXIT_IF_NOT_FOUND ; then + echo "$ROOT_PATH/$PROJECT_NAME_FILE does not exist... Exiting..." >&2 + exit 1 + fi + # Simply return a failure in case we were asked not to fail in case of + # missing file. + return 1 + fi + + if read -r PROJECT_NAME < "$ROOT_PATH/$PROJECT_NAME_FILE"; then :; else + return 1 fi } +# Check if the current folder is the Mbed TLS root one. +# +# Warning: if this is not run from Mbed TLS/TF-PSA-Crypto root folder, the +# script is terminated with a failure. in_mbedtls_repo () { - read_project_name_file + read_project_name_file true . test "$PROJECT_NAME" = "Mbed TLS" } +# Check if the current folder is the TF-PSA-Crypto root one. +# +# Warning: if this is not run from Mbed TLS/TF-PSA-Crypto root folder, the +# script is terminated with a failure. in_tf_psa_crypto_repo () { - read_project_name_file + read_project_name_file true . + test "$PROJECT_NAME" = "TF-PSA-Crypto" +} + +# Check if $1 is an Mbed TLS root folder. +# +# Differently from in_mbedtls_repo() above, this can be run on any folder +# without causing the script to exit. +is_mbedtls_root() { + if ! read_project_name_file false $1 ; then + return 1 + fi + + test "$PROJECT_NAME" = "Mbed TLS" +} + +# Check if $1 is a TF-PSA-Crypto root folder. +# +# Differently from in_tf_psa_crypto_repo() above, this can be run on any folder +# without causing the script to exit. +is_tf_psa_crypto_root() { + if ! read_project_name_file false $1 ; then + return 1 + fi + test "$PROJECT_NAME" = "TF-PSA-Crypto" } diff --git a/scripts/test_psa_compliance.py b/scripts/test_psa_compliance.py index c5e9748ca..80535805d 100755 --- a/scripts/test_psa_compliance.py +++ b/scripts/test_psa_compliance.py @@ -17,6 +17,7 @@ import shutil import subprocess import sys from typing import List +from pathlib import Path from mbedtls_framework import build_tree @@ -33,43 +34,21 @@ PSA_ARCH_TESTS_REF = 'v23.06_API1.5_ADAC_EAC' #pylint: disable=too-many-branches,too-many-statements,too-many-locals def main(library_build_dir: str): root_dir = os.getcwd() + install_dir = Path(library_build_dir + "/install_dir").resolve() + tmp_env = os.environ + tmp_env['CC'] = 'gcc' + subprocess.check_call(['cmake', '.', '-GUnix Makefiles', + '-B' + library_build_dir, + '-DCMAKE_INSTALL_PREFIX=' + str(install_dir)], + env=tmp_env) + subprocess.check_call(['cmake', '--build', library_build_dir, '--target', 'install']) - in_tf_psa_crypto_repo = build_tree.looks_like_tf_psa_crypto_root(root_dir) - - crypto_name = build_tree.crypto_library_filename(root_dir) - - # Temporary, while the crypto library is still located in the library - # directory. This will not be the case anymore when it will be built by - # the TF-PSA-Crypto build system. - if in_tf_psa_crypto_repo: - library_subdir = build_tree.crypto_core_directory(root_dir, relative=True) + if build_tree.is_mbedtls_3_6(): + libraries_to_link = [str(install_dir.joinpath("lib/libmbedcrypto.a"))] else: - library_subdir = 'library' - - crypto_lib_filename = (library_build_dir + '/' + - library_subdir + '/' + - 'lib' + crypto_name + '.a') - - # Temporary while the PSA compliance test suite is still run as part - # of Mbed TLS testing. When it is not the case anymore, the last case - # can be removed. - if in_tf_psa_crypto_repo: - extra_includes = ';{}/drivers/builtin/include'.format(root_dir) - elif build_tree.is_mbedtls_3_6(): - extra_includes = '' - else: - extra_includes = ';{}/tf-psa-crypto/include'.format(root_dir) + \ - (';{}/tf-psa-crypto/drivers/builtin/include'.format(root_dir)) - - if not os.path.exists(crypto_lib_filename): - #pylint: disable=bad-continuation - subprocess.check_call([ - 'cmake', '.', - '-GUnix Makefiles', - '-B' + library_build_dir - ]) - subprocess.check_call(['cmake', '--build', library_build_dir, - '--target', crypto_name]) + libraries_to_link = [str(install_dir.joinpath("lib/" + lib)) + for lib in ["libtfpsacrypto.a", "libbuiltin.a", + "libp256m.a", "libeverest.a"]] psa_arch_tests_dir = 'psa-arch-tests' os.makedirs(psa_arch_tests_dir, exist_ok=True) @@ -96,10 +75,10 @@ def main(library_build_dir: str): '-DTARGET=tgt_dev_apis_stdc', '-DTOOLCHAIN=HOST_GCC', '-DSUITE=CRYPTO', - '-DPSA_CRYPTO_LIB_FILENAME={}/{}'.format(root_dir, - crypto_lib_filename), - ('-DPSA_INCLUDE_PATHS={}/include' + extra_includes).format(root_dir) + '-DPSA_CRYPTO_LIB_FILENAME={}'.format(';'.join(libraries_to_link)), + '-DPSA_INCLUDE_PATHS=' + str(install_dir.joinpath("include")) ]) + subprocess.check_call(['cmake', '--build', '.']) proc = subprocess.Popen(['./psa-arch-tests-crypto'], diff --git a/tests/include/test/psa_exercise_key.h b/tests/include/test/psa_exercise_key.h index f44608b4b..b0e318144 100644 --- a/tests/include/test/psa_exercise_key.h +++ b/tests/include/test/psa_exercise_key.h @@ -138,11 +138,22 @@ int mbedtls_test_psa_setup_key_derivation_wrap( size_t capacity, int key_destroyable); /** Perform a key agreement using the given key pair against its public key - * using psa_raw_key_agreement() and psa_key_agreement(). + * (not combined with a key derivation). * - * The result is discarded. The purpose of this function is to smoke-test a key. + * The result is discarded. Thus this function can be used for smoke-testing + * a key, and to validate input validation, but not to validate results. * - * In case of failure, mark the current test case as failed. + * Depending on the library version, there can be multiple interfaces for key + * agreement. This test function performs the ones that are available amongst: + * - psa_raw_key_agreement() + * - psa_key_agreement() + * - psa_key_agreement_iop_setup() and psa_key_agreement_iop_complete() + * + * Mark the current test case as failed in the following cases: + * - Operational errors such as failure to allocate memory for an intermediate + * buffer. + * - Results are not consistent between the methods that are performed: + * different statuses, or inconsistent metadata, or different shared secret. * * \param alg A key agreement algorithm compatible with \p key. * \param key A key that allows key agreement with \p alg. @@ -150,7 +161,7 @@ int mbedtls_test_psa_setup_key_derivation_wrap( * or the key being destroyed mid-operation will only * be reported if the error code is unexpected. * - * \return \c 1 on success, \c 0 on failure. + * \return The status from psa_raw_key_agreement(). */ psa_status_t mbedtls_test_psa_raw_key_agreement_with_self( psa_algorithm_t alg, diff --git a/tests/programs/dlopen_demo.sh b/tests/programs/dlopen_demo.sh new file mode 100755 index 000000000..f93935b74 --- /dev/null +++ b/tests/programs/dlopen_demo.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +# Run the shared library dynamic loading demo program. +# This is only expected to work when Mbed TLS is built as a shared library. + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +SCRIPT_DIR=$(dirname "$0") +. "${SCRIPT_DIR}/../../scripts/demo_common.sh" + +msg "Test the dynamic loading of libmbed*" + +# Once demo_common.sh is sourced we'll have $root_dir pointing to the root +# path of Mbed TLS or TF-PSA-Crypto. +if is_mbedtls_root $root_dir; then + msg "Running in Mbed TLS repo" + program="$root_dir/programs/test/dlopen" + library_dir="$root_dir/library" +else + msg "Running in TF-PSA-Crypto repo" + program="$root_dir/programs/test/tfpsacrypto_dlopen" + library_dir="$root_dir/core" +fi + +# Skip this test if we don't have a shared library build. Detect this +# through the absence of the demo program. +if [ ! -e "$program" ]; then + msg "Error: demo program $program not found." + # Exit with a success status so that this counts as a pass for run_demos.py. + exit +fi + +# ELF-based Unix-like (Linux, *BSD, Solaris, ...) +if [ -n "${LD_LIBRARY_PATH-}" ]; then + LD_LIBRARY_PATH="$library_dir:$LD_LIBRARY_PATH" +else + LD_LIBRARY_PATH="$library_dir" +fi +export LD_LIBRARY_PATH + +# OSX/macOS +if [ -n "${DYLD_LIBRARY_PATH-}" ]; then + DYLD_LIBRARY_PATH="$library_dir:$DYLD_LIBRARY_PATH" +else + DYLD_LIBRARY_PATH="$library_dir" +fi +export DYLD_LIBRARY_PATH + +msg "Running dynamic loading test program: $program" +msg "Loading libraries from: $library_dir" +"$program" diff --git a/tests/programs/metatest.c b/tests/programs/metatest.c new file mode 100644 index 000000000..f39cb545d --- /dev/null +++ b/tests/programs/metatest.c @@ -0,0 +1,484 @@ +/** \file metatest.c + * + * \brief Test features of the test framework. + * + * When you run this program, it runs a single "meta-test". A meta-test + * performs an operation which should be caught as a failure by our + * test framework. The meta-test passes if this program calls `exit` with + * a nonzero status, or aborts, or is terminated by a signal, or if the + * framework running the program considers the run an error (this happens + * with Valgrind for a memory leak). The non-success of the meta-test + * program means that the test failure has been caught correctly. + * + * Some failures are purely functional: the logic of the code causes the + * test result to be set to FAIL. Other failures come from extra + * instrumentation which is not present in a normal build; for example, + * Asan or Valgrind to detect memory leaks. This is reflected by the + * "platform" associated with each meta-test. + * + * Use the companion script `tests/scripts/run-metatests.sh` to run all + * the meta-tests for a given platform and validate that they trigger a + * detected failure as expected. + */ + +/* + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ + + +#include +#include +#include +#include "test/helpers.h" +#include "test/threading_helpers.h" +#include "test/macros.h" +#include "test/memory.h" +#include "common.h" + +#include +#include + +#if defined(MBEDTLS_THREADING_C) +#include +#endif + + +/* This is an external variable, so the compiler doesn't know that we're never + * changing its value. + */ +volatile int false_but_the_compiler_does_not_know = 0; + +/* Hide calls to calloc/free from static checkers such as + * `gcc-12 -Wuse-after-free`, to avoid compile-time complaints about + * code where we do mean to cause a runtime error. */ +void * (* volatile calloc_but_the_compiler_does_not_know)(size_t, size_t) = mbedtls_calloc; +void(*volatile free_but_the_compiler_does_not_know)(void *) = mbedtls_free; + +/* Set n bytes at the address p to all-bits-zero, in such a way that + * the compiler should not know that p is all-bits-zero. */ +static void set_to_zero_but_the_compiler_does_not_know(volatile void *p, size_t n) +{ + memset((void *) p, false_but_the_compiler_does_not_know, n); +} + +/* Simulate an access to the given object, to avoid compiler optimizations + * in code that prepares or consumes the object. */ +static void do_nothing_with_object(void *p) +{ + (void) p; +} +void(*volatile do_nothing_with_object_but_the_compiler_does_not_know)(void *) = + do_nothing_with_object; + + +/****************************************************************/ +/* Test framework features */ +/****************************************************************/ + +static void meta_test_fail(const char *name) +{ + (void) name; + mbedtls_test_fail("Forced test failure", __LINE__, __FILE__); +} + +static void meta_test_not_equal(const char *name) +{ + int left = 20; + int right = 10; + + (void) name; + + TEST_EQUAL(left, right); +exit: + ; +} + +static void meta_test_not_le_s(const char *name) +{ + int left = 20; + int right = 10; + + (void) name; + + TEST_LE_S(left, right); +exit: + ; +} + +static void meta_test_not_le_u(const char *name) +{ + size_t left = 20; + size_t right = 10; + + (void) name; + + TEST_LE_U(left, right); +exit: + ; +} + +/****************************************************************/ +/* Platform features */ +/****************************************************************/ + +static void null_pointer_dereference(const char *name) +{ + (void) name; + volatile char *volatile p; + set_to_zero_but_the_compiler_does_not_know(&p, sizeof(p)); + /* Undefined behavior (read from null data pointer) */ + mbedtls_printf("%p -> %u\n", (void *) p, (unsigned) *p); +} + +static void null_pointer_call(const char *name) +{ + (void) name; + unsigned(*volatile p)(void); + set_to_zero_but_the_compiler_does_not_know(&p, sizeof(p)); + /* Undefined behavior (execute null function pointer) */ + /* The pointer representation may be truncated, but we don't care: + * the only point of printing it is to have some use of the pointer + * to dissuade the compiler from optimizing it away. */ + mbedtls_printf("%lx() -> %u\n", (unsigned long) (uintptr_t) p, p()); +} + + +/****************************************************************/ +/* Memory */ +/****************************************************************/ + +static void read_after_free(const char *name) +{ + (void) name; + volatile char *p = calloc_but_the_compiler_does_not_know(1, 1); + *p = 'a'; + free_but_the_compiler_does_not_know((void *) p); + /* Undefined behavior (read after free) */ + mbedtls_printf("%u\n", (unsigned) *p); +} + +static void double_free(const char *name) +{ + (void) name; + volatile char *p = calloc_but_the_compiler_does_not_know(1, 1); + *p = 'a'; + free_but_the_compiler_does_not_know((void *) p); + /* Undefined behavior (double free) */ + free_but_the_compiler_does_not_know((void *) p); +} + +static void read_uninitialized_stack(const char *name) +{ + (void) name; + char buf[1]; + if (false_but_the_compiler_does_not_know) { + buf[0] = '!'; + } + char *volatile p = buf; + if (*p != 0) { + /* Unspecified result (read from uninitialized memory) */ + mbedtls_printf("%u\n", (unsigned) *p); + } +} + +static void memory_leak(const char *name) +{ + (void) name; + volatile char *p = calloc_but_the_compiler_does_not_know(1, 1); + mbedtls_printf("%u\n", (unsigned) *p); + /* Leak of a heap object */ +} + +/* name = "test_memory_poison_%(start)_%(offset)_%(count)_%(direction)" + * Poison a region starting at start from an 8-byte aligned origin, + * encompassing count bytes. Access the region at offset from the start. + * %(start), %(offset) and %(count) are decimal integers. + * %(direction) is either the character 'r' for read or 'w' for write. + */ +static void test_memory_poison(const char *name) +{ + size_t start = 0, offset = 0, count = 0; + char direction = 'r'; + if (sscanf(name, + "%*[^0-9]%" MBEDTLS_PRINTF_SIZET + "%*[^0-9]%" MBEDTLS_PRINTF_SIZET + "%*[^0-9]%" MBEDTLS_PRINTF_SIZET + "_%c", + &start, &offset, &count, &direction) != 4) { + mbedtls_fprintf(stderr, "%s: Bad name format: %s\n", __func__, name); + return; + } + + union { + long long ll; + unsigned char buf[32]; + } aligned; + memset(aligned.buf, 'a', sizeof(aligned.buf)); + + if (start > sizeof(aligned.buf)) { + mbedtls_fprintf(stderr, + "%s: start=%" MBEDTLS_PRINTF_SIZET + " > size=%" MBEDTLS_PRINTF_SIZET, + __func__, start, sizeof(aligned.buf)); + return; + } + if (start + count > sizeof(aligned.buf)) { + mbedtls_fprintf(stderr, + "%s: start+count=%" MBEDTLS_PRINTF_SIZET + " > size=%" MBEDTLS_PRINTF_SIZET, + __func__, start + count, sizeof(aligned.buf)); + return; + } + if (offset >= count) { + mbedtls_fprintf(stderr, + "%s: offset=%" MBEDTLS_PRINTF_SIZET + " >= count=%" MBEDTLS_PRINTF_SIZET, + __func__, offset, count); + return; + } + + MBEDTLS_TEST_MEMORY_POISON(aligned.buf + start, count); + + if (direction == 'w') { + aligned.buf[start + offset] = 'b'; + do_nothing_with_object_but_the_compiler_does_not_know(aligned.buf); + } else { + do_nothing_with_object_but_the_compiler_does_not_know(aligned.buf); + mbedtls_printf("%u\n", (unsigned) aligned.buf[start + offset]); + } +} + + +/****************************************************************/ +/* Threading */ +/****************************************************************/ + +static void mutex_lock_not_initialized(const char *name) +{ + (void) name; +#if defined(MBEDTLS_THREADING_C) + mbedtls_threading_mutex_t mutex; + memset(&mutex, 0, sizeof(mutex)); + /* This mutex usage error is detected by our test framework's mutex usage + * verification framework. See framework/tests/src/threading_helpers.c. Other + * threading implementations (e.g. pthread without our instrumentation) + * might consider this normal usage. */ + TEST_ASSERT(mbedtls_mutex_lock(&mutex) == 0); +exit: + ; +#endif +} + +static void mutex_unlock_not_initialized(const char *name) +{ + (void) name; +#if defined(MBEDTLS_THREADING_C) + mbedtls_threading_mutex_t mutex; + memset(&mutex, 0, sizeof(mutex)); + /* This mutex usage error is detected by our test framework's mutex usage + * verification framework. See framework/tests/src/threading_helpers.c. Other + * threading implementations (e.g. pthread without our instrumentation) + * might consider this normal usage. */ + TEST_ASSERT(mbedtls_mutex_unlock(&mutex) == 0); +exit: + ; +#endif +} + +static void mutex_free_not_initialized(const char *name) +{ + (void) name; +#if defined(MBEDTLS_THREADING_C) + mbedtls_threading_mutex_t mutex; + memset(&mutex, 0, sizeof(mutex)); + /* This mutex usage error is detected by our test framework's mutex usage + * verification framework. See framework/tests/src/threading_helpers.c. Other + * threading implementations (e.g. pthread without our instrumentation) + * might consider this normal usage. */ + mbedtls_mutex_free(&mutex); +#endif +} + +static void mutex_double_init(const char *name) +{ + (void) name; +#if defined(MBEDTLS_THREADING_C) + mbedtls_threading_mutex_t mutex; + mbedtls_mutex_init(&mutex); + /* This mutex usage error is detected by our test framework's mutex usage + * verification framework. See framework/tests/src/threading_helpers.c. Other + * threading implementations (e.g. pthread without our instrumentation) + * might consider this normal usage. */ + mbedtls_mutex_init(&mutex); + mbedtls_mutex_free(&mutex); +#endif +} + +static void mutex_double_free(const char *name) +{ + (void) name; +#if defined(MBEDTLS_THREADING_C) + mbedtls_threading_mutex_t mutex; + mbedtls_mutex_init(&mutex); + mbedtls_mutex_free(&mutex); + /* This mutex usage error is detected by our test framework's mutex usage + * verification framework. See framework/tests/src/threading_helpers.c. Other + * threading implementations (e.g. pthread without our instrumentation) + * might consider this normal usage. */ + mbedtls_mutex_free(&mutex); +#endif +} + +static void mutex_leak(const char *name) +{ + (void) name; +#if defined(MBEDTLS_THREADING_C) + mbedtls_threading_mutex_t mutex; + mbedtls_mutex_init(&mutex); +#endif + /* This mutex usage error is detected by our test framework's mutex usage + * verification framework. See framework/tests/src/threading_helpers.c. Other + * threading implementations (e.g. pthread without our instrumentation) + * might consider this normal usage. */ +} + + +/****************************************************************/ +/* Command line entry point */ +/****************************************************************/ + +typedef struct { + /** Command line argument that will trigger that metatest. + * + * Conventionally matches "[a-z0-9_]+". */ + const char *name; + + /** Platform under which that metatest is valid. + * + * - "any": should work anywhere. + * - "asan": triggers ASan (Address Sanitizer). + * - "msan": triggers MSan (Memory Sanitizer). + * - "pthread": requires MBEDTLS_THREADING_PTHREAD and MBEDTLS_TEST_HOOKS, + * which enables MBEDTLS_TEST_MUTEX_USAGE internally in the test + * framework (see framework/tests/src/threading_helpers.c). + */ + const char *platform; + + /** Function that performs the metatest. + * + * The function receives the name as an argument. This allows using the + * same function to perform multiple variants of a test based on the name. + * + * When executed on a conforming platform, the function is expected to + * either cause a test failure (mbedtls_test_fail()), or cause the + * program to abort in some way (e.g. by causing a segfault or by + * triggering a sanitizer). + * + * When executed on a non-conforming platform, the function may return + * normally or may have unpredictable behavior. + */ + void (*entry_point)(const char *name); +} metatest_t; + +/* The list of available meta-tests. Remember to register new functions here! + * + * Note that we always compile all the functions, so that `metatest --list` + * will always list all the available meta-tests. + * + * See the documentation of metatest_t::platform for the meaning of + * platform values. + */ +metatest_t metatests[] = { + { "test_fail", "any", meta_test_fail }, + { "test_not_equal", "any", meta_test_not_equal }, + { "test_not_le_s", "any", meta_test_not_le_s }, + { "test_not_le_u", "any", meta_test_not_le_u }, + { "null_dereference", "any", null_pointer_dereference }, + { "null_call", "any", null_pointer_call }, + { "read_after_free", "asan", read_after_free }, + { "double_free", "asan", double_free }, + { "read_uninitialized_stack", "msan", read_uninitialized_stack }, + { "memory_leak", "asan", memory_leak }, + { "test_memory_poison_0_0_8_r", "poison", test_memory_poison }, + { "test_memory_poison_0_0_8_w", "poison", test_memory_poison }, + { "test_memory_poison_0_7_8_r", "poison", test_memory_poison }, + { "test_memory_poison_0_7_8_w", "poison", test_memory_poison }, + { "test_memory_poison_0_0_1_r", "poison", test_memory_poison }, + { "test_memory_poison_0_0_1_w", "poison", test_memory_poison }, + { "test_memory_poison_0_1_2_r", "poison", test_memory_poison }, + { "test_memory_poison_0_1_2_w", "poison", test_memory_poison }, + { "test_memory_poison_7_0_8_r", "poison", test_memory_poison }, + { "test_memory_poison_7_0_8_w", "poison", test_memory_poison }, + { "test_memory_poison_7_7_8_r", "poison", test_memory_poison }, + { "test_memory_poison_7_7_8_w", "poison", test_memory_poison }, + { "test_memory_poison_7_0_1_r", "poison", test_memory_poison }, + { "test_memory_poison_7_0_1_w", "poison", test_memory_poison }, + { "test_memory_poison_7_1_2_r", "poison", test_memory_poison }, + { "test_memory_poison_7_1_2_w", "poison", test_memory_poison }, + { "mutex_lock_not_initialized", "pthread", mutex_lock_not_initialized }, + { "mutex_unlock_not_initialized", "pthread", mutex_unlock_not_initialized }, + { "mutex_free_not_initialized", "pthread", mutex_free_not_initialized }, + { "mutex_double_init", "pthread", mutex_double_init }, + { "mutex_double_free", "pthread", mutex_double_free }, + { "mutex_leak", "pthread", mutex_leak }, + { NULL, NULL, NULL } +}; + +static void help(FILE *out, const char *argv0) +{ + mbedtls_fprintf(out, "Usage: %s list|TEST\n", argv0); + mbedtls_fprintf(out, "Run a meta-test that should cause a test failure.\n"); + mbedtls_fprintf(out, "With 'list', list the available tests and their platform requirement.\n"); +} + +int main(int argc, char *argv[]) +{ + const char *argv0 = argc > 0 ? argv[0] : "metatest"; + if (argc != 2) { + help(stderr, argv0); + mbedtls_exit(MBEDTLS_EXIT_FAILURE); + } + + /* Support "-help", "--help", "--list", etc. */ + const char *command = argv[1]; + while (*command == '-') { + ++command; + } + + if (strcmp(argv[1], "help") == 0) { + help(stdout, argv0); + mbedtls_exit(MBEDTLS_EXIT_SUCCESS); + } + if (strcmp(argv[1], "list") == 0) { + for (const metatest_t *p = metatests; p->name != NULL; p++) { + mbedtls_printf("%s %s\n", p->name, p->platform); + } + mbedtls_exit(MBEDTLS_EXIT_SUCCESS); + } + +#if defined(MBEDTLS_TEST_MUTEX_USAGE) + mbedtls_test_mutex_usage_init(); +#endif + + for (const metatest_t *p = metatests; p->name != NULL; p++) { + if (strcmp(argv[1], p->name) == 0) { + mbedtls_printf("Running metatest %s...\n", argv[1]); + p->entry_point(argv[1]); +#if defined(MBEDTLS_TEST_MUTEX_USAGE) + mbedtls_test_mutex_usage_check(); +#endif + int result = (int) mbedtls_test_get_result(); + + mbedtls_printf("Running metatest %s... done, result=%d\n", + argv[1], result); + mbedtls_exit(result == MBEDTLS_TEST_RESULT_SUCCESS ? + MBEDTLS_EXIT_SUCCESS : + MBEDTLS_EXIT_FAILURE); + } + } + + mbedtls_fprintf(stderr, "%s: FATAL: No such metatest: %s\n", + argv0, command); + mbedtls_exit(MBEDTLS_EXIT_FAILURE); +} diff --git a/tests/programs/query_compile_time_config.c b/tests/programs/query_compile_time_config.c new file mode 100644 index 000000000..a70e6daef --- /dev/null +++ b/tests/programs/query_compile_time_config.c @@ -0,0 +1,66 @@ +/* + * Query the Mbed TLS compile time configuration + * + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ + +#include "mbedtls/build_info.h" + +#include "mbedtls/platform.h" + +#define USAGE \ + "usage: %s [ -all | -any | -l ] ...\n\n" \ + "This program takes command line arguments which correspond to\n" \ + "the string representation of Mbed TLS compile time configurations.\n\n" \ + "If \"--all\" and \"--any\" are not used, then, if all given arguments\n" \ + "are defined in the Mbed TLS build, 0 is returned; otherwise 1 is\n" \ + "returned. Macro expansions of configurations will be printed (if any).\n" \ + "-l\tPrint all available configuration.\n" \ + "-all\tReturn 0 if all configurations are defined. Otherwise, return 1\n" \ + "-any\tReturn 0 if any configuration is defined. Otherwise, return 1\n" \ + "-h\tPrint this usage\n" + +#include +#include "query_config.h" + +int main(int argc, char *argv[]) +{ + int i; + + if (argc < 2 || strcmp(argv[1], "-h") == 0) { + mbedtls_printf(USAGE, argv[0]); + return MBEDTLS_EXIT_FAILURE; + } + + if (strcmp(argv[1], "-l") == 0) { + list_config(); + return 0; + } + + if (strcmp(argv[1], "-all") == 0) { + for (i = 2; i < argc; i++) { + if (query_config(argv[i]) != 0) { + return 1; + } + } + return 0; + } + + if (strcmp(argv[1], "-any") == 0) { + for (i = 2; i < argc; i++) { + if (query_config(argv[i]) == 0) { + return 0; + } + } + return 1; + } + + for (i = 1; i < argc; i++) { + if (query_config(argv[i]) != 0) { + return 1; + } + } + + return 0; +} diff --git a/tests/programs/query_config.h b/tests/programs/query_config.h new file mode 100644 index 000000000..43f120bf0 --- /dev/null +++ b/tests/programs/query_config.h @@ -0,0 +1,34 @@ +/* + * Query Mbed TLS compile time configurations from mbedtls_config.h + * + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ + +#ifndef MBEDTLS_PROGRAMS_TEST_QUERY_CONFIG_H +#define MBEDTLS_PROGRAMS_TEST_QUERY_CONFIG_H + +#include "mbedtls/build_info.h" + +/** Check whether a given configuration symbol is enabled. + * + * \param config The symbol to query (e.g. "MBEDTLS_RSA_C"). + * \return \c 0 if the symbol was defined at compile time + * (in MBEDTLS_CONFIG_FILE or mbedtls_config.h), + * \c 1 otherwise. + * + * \note This function is defined in `programs/test/query_config.c` + * which is automatically generated by + * `scripts/generate_query_config.pl`. + */ +int query_config(const char *config); + +/** List all enabled configuration symbols + * + * \note This function is defined in `programs/test/query_config.c` + * which is automatically generated by + * `scripts/generate_query_config.pl`. + */ +void list_config(void); + +#endif /* MBEDTLS_PROGRAMS_TEST_QUERY_CONFIG_H */ diff --git a/tests/programs/query_included_headers.c b/tests/programs/query_included_headers.c new file mode 100644 index 000000000..cdafa1620 --- /dev/null +++ b/tests/programs/query_included_headers.c @@ -0,0 +1,29 @@ +/* Ad hoc report on included headers. */ +/* + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ + +#include +#include + +int main(void) +{ + + /* Which PSA platform header? */ +#if defined(PSA_CRYPTO_PLATFORM_H) + mbedtls_printf("PSA_CRYPTO_PLATFORM_H\n"); +#endif +#if defined(PSA_CRYPTO_PLATFORM_ALT_H) + mbedtls_printf("PSA_CRYPTO_PLATFORM_ALT_H\n"); +#endif + + /* Which PSA struct header? */ +#if defined(PSA_CRYPTO_STRUCT_H) + mbedtls_printf("PSA_CRYPTO_STRUCT_H\n"); +#endif +#if defined(PSA_CRYPTO_STRUCT_ALT_H) + mbedtls_printf("PSA_CRYPTO_STRUCT_ALT_H\n"); +#endif + +} diff --git a/tests/programs/test_zeroize.gdb b/tests/programs/test_zeroize.gdb new file mode 100644 index 000000000..6eaf61ba6 --- /dev/null +++ b/tests/programs/test_zeroize.gdb @@ -0,0 +1,64 @@ +# test_zeroize.gdb +# +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +# +# Purpose +# +# Run a test using the debugger to check that the mbedtls_platform_zeroize() +# function in platform_util.h is not being optimized out by the compiler. To do +# so, the script loads the test program at programs/test/zeroize and sets a +# breakpoint at the last return statement in main(). When the breakpoint is +# hit, the debugger manually checks the contents to be zeroized and checks that +# it is actually cleared. +# +# The mbedtls_platform_zeroize() test is debugger driven because there does not +# seem to be a mechanism to reliably check whether the zeroize calls are being +# eliminated by compiler optimizations from within the compiled program. The +# problem is that a compiler would typically remove what it considers to be +# "unnecessary" assignments as part of redundant code elimination. To identify +# such code, the compilar will create some form dependency graph between +# reads and writes to variables (among other situations). It will then use this +# data structure to remove redundant code that does not have an impact on the +# program's observable behavior. In the case of mbedtls_platform_zeroize(), an +# intelligent compiler could determine that this function clears a block of +# memory that is not accessed later in the program, so removing the call to +# mbedtls_platform_zeroize() does not have an observable behavior. However, +# inserting a test after a call to mbedtls_platform_zeroize() to check whether +# the block of memory was correctly zeroed would force the compiler to not +# eliminate the mbedtls_platform_zeroize() call. If this does not occur, then +# the compiler potentially has a bug. +# +# Note: This test requires that the test program is compiled with -g3. + +set confirm off + +file ./programs/test/zeroize + +search GDB_BREAK_HERE +break $_ + +set args ./framework/tests/programs/zeroize.c +run + +set $i = 0 +set $len = sizeof(buf) +set $buf = buf + +while $i < $len + if $buf[$i++] != 0 + echo The buffer at was not zeroized\n + quit 1 + end +end + +echo The buffer was correctly zeroized\n + +continue + +if $_exitcode != 0 + echo The program did not terminate correctly\n + quit 1 +end + +quit 0 diff --git a/tests/programs/zeroize.c b/tests/programs/zeroize.c new file mode 100644 index 000000000..d81358e81 --- /dev/null +++ b/tests/programs/zeroize.c @@ -0,0 +1,72 @@ +/* + * Zeroize application for debugger-driven testing + * + * This is a simple test application used for debugger-driven testing to check + * whether calls to mbedtls_platform_zeroize() are being eliminated by compiler + * optimizations. This application is used by the GDB script at + * tests/programs/test_zeroize.gdb: the script sets a breakpoint at the last + * return statement in the main() function of this program. The debugger + * facilities are then used to manually inspect the memory and verify that the + * call to mbedtls_platform_zeroize() was not eliminated. + * + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ + +#include "mbedtls/build_info.h" + +#include + +#include "mbedtls/platform.h" + +#include "mbedtls/platform_util.h" + +#define BUFFER_LEN 1024 + +static void usage(void) +{ + mbedtls_printf("Zeroize is a simple program to assist with testing\n"); + mbedtls_printf("the mbedtls_platform_zeroize() function by using the\n"); + mbedtls_printf("debugger. This program takes a file as input and\n"); + mbedtls_printf("prints the first %d characters. Usage:\n\n", BUFFER_LEN); + mbedtls_printf(" zeroize \n"); +} + +int main(int argc, char **argv) +{ + int exit_code = MBEDTLS_EXIT_FAILURE; + FILE *fp; + char buf[BUFFER_LEN]; + char *p = buf; + char *end = p + BUFFER_LEN; + int c; + + if (argc != 2) { + mbedtls_printf("This program takes exactly 1 argument\n"); + usage(); + mbedtls_exit(exit_code); + } + + fp = fopen(argv[1], "r"); + if (fp == NULL) { + mbedtls_printf("Could not open file '%s'\n", argv[1]); + mbedtls_exit(exit_code); + } + + while ((c = fgetc(fp)) != EOF && p < end - 1) { + *p++ = (char) c; + } + *p = '\0'; + + if (p - buf != 0) { + mbedtls_printf("%s\n", buf); + exit_code = MBEDTLS_EXIT_SUCCESS; + } else { + mbedtls_printf("The file is empty!\n"); + } + + fclose(fp); + mbedtls_platform_zeroize(buf, sizeof(buf)); + + mbedtls_exit(exit_code); // GDB_BREAK_HERE -- don't remove this comment! +} diff --git a/tests/src/fake_external_rng_for_test.c b/tests/src/fake_external_rng_for_test.c index c0bfde51a..1eae045f3 100644 --- a/tests/src/fake_external_rng_for_test.c +++ b/tests/src/fake_external_rng_for_test.c @@ -1,6 +1,11 @@ /** \file fake_external_rng_for_test.c * - * \brief Helper functions to test PSA crypto functionality. + * Helper functions to test external functions: + * - mbedtls_psa_external_get_random() + * - mbedtls_platform_get_entropy_alt() + * + * These functions are provided only for test purposes and they should not be + * used for production. */ /* @@ -11,6 +16,7 @@ #include #if defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) + #include #include @@ -42,4 +48,25 @@ psa_status_t mbedtls_psa_external_get_random( *output_length = output_size; return PSA_SUCCESS; } + #endif /* MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ + +#if defined(MBEDTLS_PLATFORM_GET_ENTROPY_ALT) + +#include +# include + +int mbedtls_platform_get_entropy_alt(unsigned char *output, size_t output_size, + size_t *output_len, size_t *entropy_content) +{ + mbedtls_test_rnd_std_rand(NULL, output, output_size); + + *output_len = output_size; + if (entropy_content != NULL) { + *entropy_content = output_size * 8; + } + + return 0; +} + +#endif /* MBEDTLS_PLATFORM_GET_ENTROPY_ALT */ diff --git a/tests/src/psa_crypto_stubs.c b/tests/src/psa_crypto_stubs.c index 81d7f4b32..8d7ba334e 100644 --- a/tests/src/psa_crypto_stubs.c +++ b/tests/src/psa_crypto_stubs.c @@ -72,4 +72,62 @@ psa_status_t psa_import_key(const psa_key_attributes_t *attributes, return PSA_ERROR_COMMUNICATION_FAILURE; } +int psa_can_do_hash(psa_algorithm_t hash_alg) +{ + (void) hash_alg; + return 0; +} + +psa_status_t psa_hash_clone(const psa_hash_operation_t *source_operation, + psa_hash_operation_t *target_operation) +{ + (void) source_operation; + (void) target_operation; + return PSA_ERROR_COMMUNICATION_FAILURE; +} + +psa_status_t psa_hash_setup(psa_hash_operation_t *operation, + psa_algorithm_t alg) +{ + (void) operation; + (void) alg; + return PSA_ERROR_COMMUNICATION_FAILURE; +} + +psa_status_t psa_hash_update(psa_hash_operation_t *operation, + const uint8_t *input_external, + size_t input_length) +{ + (void) operation; + (void) input_external; + (void) input_length; + return PSA_ERROR_COMMUNICATION_FAILURE; +} + +psa_status_t psa_hash_finish(psa_hash_operation_t *operation, + uint8_t *hash_external, + size_t hash_size, + size_t *hash_length) +{ + (void) operation; + (void) hash_external; + (void) hash_size; + (void) hash_length; + return PSA_ERROR_COMMUNICATION_FAILURE; +} + +psa_status_t psa_hash_compute(psa_algorithm_t alg, + const uint8_t *input_external, size_t input_length, + uint8_t *hash_external, size_t hash_size, + size_t *hash_length) +{ + (void) alg; + (void) input_external; + (void) input_length; + (void) hash_external; + (void) hash_size; + (void) hash_length; + return PSA_ERROR_COMMUNICATION_FAILURE; +} + #endif /* MBEDTLS_PSA_CRYPTO_CLIENT && !MBEDTLS_PSA_CRYPTO_C */ diff --git a/tests/src/psa_exercise_key.c b/tests/src/psa_exercise_key.c index b92c1f750..01fc143e7 100644 --- a/tests/src/psa_exercise_key.c +++ b/tests/src/psa_exercise_key.c @@ -732,9 +732,9 @@ psa_status_t mbedtls_test_psa_raw_key_agreement_with_self( } PSA_ASSERT(status); - status = psa_raw_key_agreement(alg, key, - public_key, public_key_length, - output, sizeof(output), &output_length); + status = psa_raw_key_agreement( + alg, key, public_key, public_key_length, + output, sizeof(output), &output_length); if (key_destroyable && status == PSA_ERROR_INVALID_HANDLE) { /* The key has been destroyed. */ status = PSA_SUCCESS; @@ -749,8 +749,11 @@ psa_status_t mbedtls_test_psa_raw_key_agreement_with_self( } #if MBEDTLS_VERSION_MAJOR >= 4 + psa_status_t raw_status = status; + psa_set_key_type(&shared_secret_attributes, PSA_KEY_TYPE_DERIVE); - psa_set_key_usage_flags(&shared_secret_attributes, PSA_KEY_USAGE_DERIVE | PSA_KEY_USAGE_EXPORT); + psa_set_key_usage_flags(&shared_secret_attributes, + PSA_KEY_USAGE_DERIVE | PSA_KEY_USAGE_EXPORT); status = psa_key_agreement(key, public_key, public_key_length, alg, &shared_secret_attributes, &shared_secret_id); @@ -759,8 +762,15 @@ psa_status_t mbedtls_test_psa_raw_key_agreement_with_self( /* The key has been destroyed. */ status = PSA_SUCCESS; goto exit; - } else if (status == PSA_SUCCESS) { + } + /* In this function, we expect either success or a validation failure, + * which should be identical for raw output and key output. So flag any + * discrepancy between the two (in particular a key creation failure) + * as a test failure. */ + TEST_EQUAL(raw_status, status); + + if (status == PSA_SUCCESS) { status = psa_get_key_attributes(shared_secret_id, &export_attributes); if (key_destroyable && status == PSA_ERROR_INVALID_HANDLE) { /* The key has been destroyed. */ @@ -768,18 +778,21 @@ psa_status_t mbedtls_test_psa_raw_key_agreement_with_self( goto exit; } - exported_size = PSA_EXPORT_KEY_OUTPUT_SIZE(psa_get_key_type(&export_attributes), - psa_get_key_bits(&export_attributes)); + exported_size = + PSA_EXPORT_KEY_OUTPUT_SIZE(psa_get_key_type(&export_attributes), + psa_get_key_bits(&export_attributes)); TEST_CALLOC(exported, exported_size); - status = psa_export_key(shared_secret_id, exported, exported_size, &exported_length); - + status = psa_export_key(shared_secret_id, + exported, exported_size, &exported_length); if (key_destroyable && status == PSA_ERROR_INVALID_HANDLE) { /* The key has been destroyed. */ status = PSA_SUCCESS; + } else { + PSA_ASSERT(status); + TEST_MEMORY_COMPARE(exported, exported_length, + output, output_length); } - - PSA_ASSERT(status); } #if defined(MBEDTLS_ECP_RESTARTABLE) && defined(MBEDTLS_PSA_BUILTIN_ALG_ECDH) @@ -798,18 +811,39 @@ psa_status_t mbedtls_test_psa_raw_key_agreement_with_self( /* The key has been destroyed. */ status = PSA_SUCCESS; goto exit; - } else if (status == PSA_SUCCESS) { + } + + /* In this function, we expect either success or a validation failure, + * which should be identical for one-shot and interruptible. For an + * interruptible operation, we insist on detecting error conditions + * early, in setup() rather than complete(). So flag any discrepancy + * between one-shot and interruptible-setup as a test failure. */ + TEST_EQUAL(raw_status, status); + + if (status == PSA_SUCCESS) { do { - status = psa_key_agreement_iop_complete(&iop_operation, &shared_secret_id); + status = psa_key_agreement_iop_complete(&iop_operation, + &shared_secret_id); } while (status == PSA_OPERATION_INCOMPLETE); if (key_destroyable && status == PSA_ERROR_INVALID_HANDLE) { /* The key has been destroyed. */ status = PSA_SUCCESS; + } else { + PSA_ASSERT(status); + status = psa_export_key(shared_secret_id, + exported, exported_size, + &exported_length); + if (key_destroyable && status == PSA_ERROR_INVALID_HANDLE) { + /* The key has been destroyed. */ + status = PSA_SUCCESS; + } else { + PSA_ASSERT(status); + TEST_MEMORY_COMPARE(exported, exported_length, + output, output_length); + } } - - PSA_ASSERT(status); } } else { TEST_EQUAL(psa_key_agreement_iop_setup(&iop_operation, key, public_key,