diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8e83e1a23..c74c49bc6 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -8,9 +8,9 @@ Please write a few sentences describing the overall goals of the pull request's Please add the numbers (or links) of the associated pull requests for consuming branches. You can omit branches where this pull request is not needed. -- [ ] **crypto PR** Mbed-TLS/TF-PSA-Crypto# -- [ ] **development PR** Mbed-TLS/mbedtls# -- [ ] **3.6 PR** Mbed-TLS/mbedtls# +- [ ] **TF-PSA-Crypto PR** provided # | not required because: +- [ ] **development PR** provided # | not required because: +- [ ] **3.6 PR** provided # | not required because: 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..aa121b6fb 100755 --- a/scripts/check-doxy-blocks.pl +++ b/scripts/check-doxy-blocks.pl @@ -16,7 +16,10 @@ 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 + 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 +56,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-python-files.sh b/scripts/check-python-files.sh index 77102ba50..a51766f71 100755 --- a/scripts/check-python-files.sh +++ b/scripts/check-python-files.sh @@ -38,7 +38,7 @@ can_pylint () { can_mypy () { # mypy 0.770 is too old: - # tests/scripts/test_psa_constant_names.py:34: error: Cannot find implementation or library stub for module named 'mbedtls_framework' + # framework/scripts/test_psa_constant_names.py:34: error: Cannot find implementation or library stub for module named 'mbedtls_framework' # mypy 0.780 from pip passed on the first commit containing this line. check_version mypy.version 0.780 } diff --git a/scripts/check_names.py b/scripts/check_names.py index 12daae090..0f5427574 100755 --- a/scripts/check_names.py +++ b/scripts/check_names.py @@ -8,12 +8,14 @@ This script confirms that the naming of all symbols and identifiers in Mbed TLS are consistent with the house style and are also self-consistent. It only runs on Linux and macOS since it depends on nm. -It contains two major Python classes, CodeParser and NameChecker. They both have -a comprehensive "run-all" function (comprehensive_parse() and perform_checks()) -but the individual functions can also be used for specific needs. +It contains three major Python classes, TFPSACryptoCodeParser, +MBEDTLSCodeParser and NameChecker. They all have a comprehensive "run-all" +function (comprehensive_parse() and perform_checks()) but the individual +functions can also be used for specific needs. -CodeParser makes heavy use of regular expressions to parse the code, and is -dependent on the current code formatting. Many Python C parser libraries require +CodeParser(a inherent base class for TFPSACryptoCodeParser and MBEDTLSCodeParser) +makes heavy use of regular expressions to parse the code, and is dependent on +the current code formatting. Many Python C parser libraries require preprocessed C code, which means no macro parsing. Compiler tools are also not very helpful when we want the exact location in the original source (which becomes impossible when e.g. comments are stripped). @@ -43,6 +45,7 @@ import enum import shutil import subprocess import logging +import tempfile import project_scripts # pylint: disable=unused-import from mbedtls_framework import build_tree @@ -212,7 +215,8 @@ class CodeParser(): """ def __init__(self, log): self.log = log - build_tree.check_repo_path() + if not build_tree.looks_like_root(os.getcwd()): + raise Exception("This script must be run from Mbed TLS or TF-PSA-Crypto root") # Memo for storing "glob expression": set(filepaths) self.files = {} @@ -221,126 +225,21 @@ class CodeParser(): # Note that "*" can match directory separators in exclude lists. self.excluded_files = ["*/bn_mul", "*/compat-2.x.h"] - def comprehensive_parse(self): + def _parse(self, all_macros, enum_consts, identifiers, + excluded_identifiers, mbed_psa_words, symbols): + # pylint: disable=too-many-arguments """ - Comprehensive ("default") function to call each parsing function and - retrieve various elements of the code, together with the source location. + Parse macros, enums, identifiers, excluded identifiers, Mbed PSA word and Symbols. Returns a dict of parsed item key to the corresponding List of Matches. """ + self.log.info("Parsing source code...") self.log.debug( "The following files are excluded from the search: {}" .format(str(self.excluded_files)) ) - all_macros = {"public": [], "internal": [], "private":[]} - if build_tree.is_mbedtls_3_6(): - all_macros["public"] = self.parse_macros([ - "include/mbedtls/*.h", - "include/psa/*.h", - "3rdparty/everest/include/everest/everest.h", - "3rdparty/everest/include/everest/x25519.h" - ]) - all_macros["internal"] = self.parse_macros([ - "library/*.h", - "framework/tests/include/test/drivers/*.h", - ]) - all_macros["private"] = self.parse_macros([ - "library/*.c", - ]) - enum_consts = self.parse_enum_consts([ - "include/mbedtls/*.h", - "include/psa/*.h", - "library/*.h", - "library/*.c", - "3rdparty/everest/include/everest/everest.h", - "3rdparty/everest/include/everest/x25519.h" - ]) - identifiers, excluded_identifiers = self.parse_identifiers([ - "include/mbedtls/*.h", - "include/psa/*.h", - "library/*.h", - "3rdparty/everest/include/everest/everest.h", - "3rdparty/everest/include/everest/x25519.h" - ], ["3rdparty/p256-m/p256-m/p256-m.h"]) - mbed_psa_words = self.parse_mbed_psa_words([ - "include/mbedtls/*.h", - "include/psa/*.h", - "library/*.h", - "3rdparty/everest/include/everest/everest.h", - "3rdparty/everest/include/everest/x25519.h", - "library/*.c", - "3rdparty/everest/library/everest.c", - "3rdparty/everest/library/x25519.c" - ], ["library/psa_crypto_driver_wrappers.h"]) - else: - all_macros["public"] = self.parse_macros([ - "include/mbedtls/*.h", - "include/psa/*.h", - "tf-psa-crypto/include/psa/*.h", - "tf-psa-crypto/include/tf-psa-crypto/*.h", - "tf-psa-crypto/drivers/builtin/include/mbedtls/*.h", - "tf-psa-crypto/drivers/everest/include/everest/everest.h", - "tf-psa-crypto/drivers/everest/include/everest/x25519.h" - ]) - all_macros["internal"] = self.parse_macros([ - "library/*.h", - "tf-psa-crypto/core/*.h", - "tf-psa-crypto/drivers/builtin/src/*.h", - "framework/tests/include/test/drivers/*.h", - ]) - all_macros["private"] = self.parse_macros([ - "library/*.c", - "tf-psa-crypto/core/*.c", - "tf-psa-crypto/drivers/builtin/src/*.c", - ]) - enum_consts = self.parse_enum_consts([ - "include/mbedtls/*.h", - "include/psa/*.h", - "tf-psa-crypto/include/psa/*.h", - "tf-psa-crypto/include/tf-psa-crypto/*.h", - "tf-psa-crypto/drivers/builtin/include/mbedtls/*.h", - "library/*.h", - "tf-psa-crypto/core/*.h", - "tf-psa-crypto/drivers/builtin/src/*.h", - "library/*.c", - "tf-psa-crypto/core/*.c", - "tf-psa-crypto/drivers/builtin/src/*.c", - "tf-psa-crypto/drivers/everest/include/everest/everest.h", - "tf-psa-crypto/drivers/everest/include/everest/x25519.h" - ]) - identifiers, excluded_identifiers = self.parse_identifiers([ - "include/mbedtls/*.h", - "include/psa/*.h", - "tf-psa-crypto/include/psa/*.h", - "tf-psa-crypto/include/tf-psa-crypto/*.h", - "tf-psa-crypto/drivers/builtin/include/mbedtls/*.h", - "library/*.h", - "tf-psa-crypto/core/*.h", - "tf-psa-crypto/drivers/builtin/src/*.h", - "tf-psa-crypto/drivers/everest/include/everest/everest.h", - "tf-psa-crypto/drivers/everest/include/everest/x25519.h" - ], ["tf-psa-crypto/drivers/p256-m/p256-m/p256-m.h"]) - mbed_psa_words = self.parse_mbed_psa_words([ - "include/mbedtls/*.h", - "include/psa/*.h", - "tf-psa-crypto/include/psa/*.h", - "tf-psa-crypto/include/tf-psa-crypto/*.h", - "tf-psa-crypto/drivers/builtin/include/mbedtls/*.h", - "library/*.h", - "tf-psa-crypto/core/*.h", - "tf-psa-crypto/drivers/builtin/src/*.h", - "tf-psa-crypto/drivers/everest/include/everest/everest.h", - "tf-psa-crypto/drivers/everest/include/everest/x25519.h", - "library/*.c", - "tf-psa-crypto/core/*.c", - "tf-psa-crypto/drivers/builtin/src/*.c", - "tf-psa-crypto/drivers/everest/library/everest.c", - "tf-psa-crypto/drivers/everest/library/x25519.c" - ], ["tf-psa-crypto/core/psa_crypto_driver_wrappers.h"]) - symbols = self.parse_symbols() - # Remove identifier macros like mbedtls_printf or mbedtls_calloc identifiers_justname = [x.name for x in identifiers] actual_macros = {"public": [], "internal": []} @@ -725,6 +624,304 @@ class CodeParser(): return (included_identifiers, excluded_identifiers) + def parse_symbols(self): + """ + Compile a library, and parse the object files using nm to retrieve the + list of referenced symbols. Exceptions thrown here are rethrown because + they would be critical errors that void several tests, and thus needs + to halt the program. This is explicitly done for clarity. + + Returns a List of unique symbols defined and used in the libraries. + """ + raise NotImplementedError("parse_symbols must be implemented by a code parser") + + def comprehensive_parse(self): + """ + (Must be defined as a class method) + Comprehensive ("default") function to call each parsing function and + retrieve various elements of the code, together with the source location. + + Returns a dict of parsed item key to the corresponding List of Matches. + """ + raise NotImplementedError("comprehension_parse must be implemented by a code parser") + + def parse_symbols_from_nm(self, object_files): + """ + Run nm to retrieve the list of referenced symbols in each object file. + Does not return the position data since it is of no use. + + Args: + * object_files: a List of compiled object filepaths to search through. + + Returns a List of unique symbols defined and used in any of the object + files. + """ + nm_undefined_regex = re.compile(r"^\S+: +U |^$|^\S+:$") + nm_valid_regex = re.compile(r"^\S+( [0-9A-Fa-f]+)* . _*(?P\w+)") + exclusions = ("FStar", "Hacl") + symbols = [] + # Gather all outputs of nm + nm_output = "" + for lib in object_files: + nm_output += subprocess.run( + ["nm", "-og", lib], + universal_newlines=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=True + ).stdout + for line in nm_output.splitlines(): + if not nm_undefined_regex.search(line): + symbol = nm_valid_regex.search(line) + if (symbol and not symbol.group("symbol").startswith(exclusions)): + symbols.append(symbol.group("symbol")) + else: + self.log.error(line) + return symbols + +class TFPSACryptoCodeParser(CodeParser): + """ + Class for retrieving files and parsing TF-PSA-Crypto code. This can be used + independently of the checks that NameChecker performs. + """ + + def __init__(self, log): + super().__init__(log) + if not build_tree.looks_like_tf_psa_crypto_root(os.getcwd()): + raise Exception("This script must be run from TF-PSA-Crypto root.") + + def comprehensive_parse(self): + """ + Comprehensive ("default") function to call each parsing function and + retrieve various elements of the code, together with the source location. + + Returns a dict of parsed item key to the corresponding List of Matches. + """ + all_macros = {"public": [], "internal": [], "private":[]} + all_macros["public"] = self.parse_macros([ + "include/psa/*.h", + "include/tf-psa-crypto/*.h", + "drivers/builtin/include/mbedtls/*.h", + "drivers/everest/include/everest/everest.h", + "drivers/everest/include/everest/x25519.h" + ]) + all_macros["internal"] = self.parse_macros([ + "core/*.h", + "drivers/builtin/src/*.h", + "framework/tests/include/test/drivers/*.h", + ]) + all_macros["private"] = self.parse_macros([ + "core/*.c", + "drivers/builtin/src/*.c", + ]) + enum_consts = self.parse_enum_consts([ + "include/psa/*.h", + "include/tf-psa-crypto/*.h", + "drivers/builtin/include/mbedtls/*.h", + "core/*.h", + "drivers/builtin/src/*.h", + "core/*.c", + "drivers/builtin/src/*.c", + "drivers/everest/include/everest/everest.h", + "drivers/everest/include/everest/x25519.h" + ]) + identifiers, excluded_identifiers = self.parse_identifiers([ + "include/psa/*.h", + "include/tf-psa-crypto/*.h", + "drivers/builtin/include/mbedtls/*.h", + "core/*.h", + "drivers/builtin/src/*.h", + "drivers/everest/include/everest/everest.h", + "drivers/everest/include/everest/x25519.h" + ], ["drivers/p256-m/p256-m/p256-m.h"]) + mbed_psa_words = self.parse_mbed_psa_words([ + "include/psa/*.h", + "include/tf-psa-crypto/*.h", + "drivers/builtin/include/mbedtls/*.h", + "core/*.h", + "drivers/builtin/src/*.h", + "drivers/everest/include/everest/everest.h", + "drivers/everest/include/everest/x25519.h", + "core/*.c", + "drivers/builtin/src/*.c", + "drivers/everest/library/everest.c", + "drivers/everest/library/x25519.c" + ], ["core/psa_crypto_driver_wrappers.h"]) + symbols = self.parse_symbols() + + return self._parse(all_macros, enum_consts, identifiers, + excluded_identifiers, mbed_psa_words, symbols) + + def parse_symbols(self): + """ + Compile the TF-PSA-Crypto libraries, and parse the + object files using nm to retrieve the list of referenced symbols. + Exceptions thrown here are rethrown because they would be critical + errors that void several tests, and thus needs to halt the program. This + is explicitly done for clarity. + + Returns a List of unique symbols defined and used in the libraries. + """ + self.log.info("Compiling...") + symbols = [] + + # Back up the config and atomically compile with the full configuration. + shutil.copy( + "include/psa/crypto_config.h", + "include/psa/crypto_config.h.bak" + ) + try: + # Use check=True in all subprocess calls so that failures are raised + # as exceptions and logged. + subprocess.run( + ["python3", "scripts/config.py", "full"], + universal_newlines=True, + check=True + ) + my_environment = os.environ.copy() + my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables" + + source_dir = os.getcwd() + build_dir = tempfile.mkdtemp() + os.chdir(build_dir) + subprocess.run( + ["cmake", "-DGEN_FILES=ON", source_dir], + universal_newlines=True, + check=True + ) + subprocess.run( + ["make"], + env=my_environment, + universal_newlines=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=True + ) + + # Perform object file analysis using nm + symbols = self.parse_symbols_from_nm([ + build_dir + "/drivers/builtin/libbuiltin.a", + build_dir + "/drivers/p256-m/libp256m.a", + build_dir + "/drivers/everest/libeverest.a", + build_dir + "/core/libtfpsacrypto.a" + ]) + + os.chdir(source_dir) + shutil.rmtree(build_dir) + except subprocess.CalledProcessError as error: + self.log.debug(error.output) + raise error + finally: + # Put back the original config regardless of there being errors. + # Works also for keyboard interrupts. + shutil.move( + "include/psa/crypto_config.h.bak", + "include/psa/crypto_config.h" + ) + + return symbols + +class MBEDTLSCodeParser(CodeParser): + """ + Class for retrieving files and parsing Mbed TLS code. This can be used + independently of the checks that NameChecker performs. + """ + + def __init__(self, log): + super().__init__(log) + if not build_tree.looks_like_mbedtls_root(os.getcwd()): + raise Exception("This script must be run from Mbed TLS root.") + + def comprehensive_parse(self): + """ + Comprehensive ("default") function to call each parsing function and + retrieve various elements of the code, together with the source location. + + Returns a dict of parsed item key to the corresponding List of Matches. + """ + all_macros = {"public": [], "internal": [], "private":[]} + # TF-PSA-Crypto is in the same repo in 3.6 so initalise variable here. + tf_psa_crypto_parse_result = {} + + if build_tree.is_mbedtls_3_6(): + all_macros["public"] = self.parse_macros([ + "include/mbedtls/*.h", + "include/psa/*.h", + "3rdparty/everest/include/everest/everest.h", + "3rdparty/everest/include/everest/x25519.h" + ]) + all_macros["internal"] = self.parse_macros([ + "library/*.h", + "framework/tests/include/test/drivers/*.h", + ]) + all_macros["private"] = self.parse_macros([ + "library/*.c", + ]) + enum_consts = self.parse_enum_consts([ + "include/mbedtls/*.h", + "include/psa/*.h", + "library/*.h", + "library/*.c", + "3rdparty/everest/include/everest/everest.h", + "3rdparty/everest/include/everest/x25519.h" + ]) + identifiers, excluded_identifiers = self.parse_identifiers([ + "include/mbedtls/*.h", + "include/psa/*.h", + "library/*.h", + "3rdparty/everest/include/everest/everest.h", + "3rdparty/everest/include/everest/x25519.h" + ], ["3rdparty/p256-m/p256-m/p256-m.h"]) + mbed_psa_words = self.parse_mbed_psa_words([ + "include/mbedtls/*.h", + "include/psa/*.h", + "library/*.h", + "3rdparty/everest/include/everest/everest.h", + "3rdparty/everest/include/everest/x25519.h", + "library/*.c", + "3rdparty/everest/library/everest.c", + "3rdparty/everest/library/x25519.c" + ], ["library/psa_crypto_driver_wrappers.h"]) + else: + all_macros = {"public": [], "internal": [], "private":[]} + all_macros["public"] = self.parse_macros([ + "include/mbedtls/*.h", + ]) + all_macros["internal"] = self.parse_macros([ + "library/*.h", + "framework/tests/include/test/drivers/*.h", + ]) + all_macros["private"] = self.parse_macros([ + "library/*.c", + ]) + enum_consts = self.parse_enum_consts([ + "include/mbedtls/*.h", + "library/*.h", + "library/*.c", + ]) + identifiers, excluded_identifiers = self.parse_identifiers([ + "include/mbedtls/*.h", + "library/*.h", + ]) + mbed_psa_words = self.parse_mbed_psa_words([ + "include/mbedtls/*.h", + "library/*.h", + "library/*.c", + ]) + os.chdir("./tf-psa-crypto") + tf_psa_crypto_code_parser = TFPSACryptoCodeParser(self.log) + tf_psa_crypto_parse_result = tf_psa_crypto_code_parser.comprehensive_parse() + os.chdir("../") + + symbols = self.parse_symbols() + mbedtls_parse_result = self._parse(all_macros, enum_consts, + identifiers, excluded_identifiers, + mbed_psa_words, symbols) + # Combile results for Mbed TLS and TF-PSA-Crypto + for key in tf_psa_crypto_parse_result: + mbedtls_parse_result[key] += tf_psa_crypto_parse_result[key] + return mbedtls_parse_result + def parse_symbols(self): """ Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509 @@ -794,44 +991,6 @@ class CodeParser(): return symbols - def parse_symbols_from_nm(self, object_files): - """ - Run nm to retrieve the list of referenced symbols in each object file. - Does not return the position data since it is of no use. - - Args: - * object_files: a List of compiled object filepaths to search through. - - Returns a List of unique symbols defined and used in any of the object - files. - """ - nm_undefined_regex = re.compile(r"^\S+: +U |^$|^\S+:$") - nm_valid_regex = re.compile(r"^\S+( [0-9A-Fa-f]+)* . _*(?P\w+)") - exclusions = ("FStar", "Hacl") - - symbols = [] - - # Gather all outputs of nm - nm_output = "" - for lib in object_files: - nm_output += subprocess.run( - ["nm", "-og", lib], - universal_newlines=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - check=True - ).stdout - - for line in nm_output.splitlines(): - if not nm_undefined_regex.search(line): - symbol = nm_valid_regex.search(line) - if (symbol and not symbol.group("symbol").startswith(exclusions)): - symbols.append(symbol.group("symbol")) - else: - self.log.error(line) - - return symbols - class NameChecker(): """ Representation of the core name checking operation performed by this script. @@ -1016,8 +1175,15 @@ def main(): log.addHandler(logging.StreamHandler()) try: - code_parser = CodeParser(log) - parse_result = code_parser.comprehensive_parse() + if build_tree.looks_like_tf_psa_crypto_root(os.getcwd()): + tf_psa_crypto_code_parser = TFPSACryptoCodeParser(log) + parse_result = tf_psa_crypto_code_parser.comprehensive_parse() + elif build_tree.looks_like_mbedtls_root(os.getcwd()): + # Mbed TLS uses TF-PSA-Crypto, so we need to parse TF-PSA-Crypto too + mbedtls_code_parser = MBEDTLSCodeParser(log) + parse_result = mbedtls_code_parser.comprehensive_parse() + else: + raise Exception("This script must be run from Mbed TLS or TF-PSA-Crypto root") except Exception: # pylint: disable=broad-except traceback.print_exc() sys.exit(2) 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..4ad4c7187 --- /dev/null +++ b/scripts/demo_common.sh @@ -0,0 +1,169 @@ +## 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 + +DEMO_COMMON_NEED_QUERY_COMPILE_TIME_CONFIG=${DEMO_COMMON_NEED_QUERY_COMPILE_TIME_CONFIG:-1} + +need_query_compile_time_config () { + if [ $DEMO_COMMON_NEED_QUERY_COMPILE_TIME_CONFIG -eq 1 ]; then + return 0; + else + return 1; + fi +} + +## 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=5 +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 we reached the Mbed TLS root folder then we're done. + if is_mbedtls_root "$root_dir"; then + break; + fi + # If we reached the TF-PSA-Crypto root folder and the script that sourced + # this file does not need query_compile_time_config (which is only available + # in Mbed TLS repo) then we're done. + if is_tf_psa_crypto_root "$root_dir" && ! need_query_compile_time_config; 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 + +## 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 new file mode 100755 index 000000000..80535805d --- /dev/null +++ b/scripts/test_psa_compliance.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +"""Run the PSA Crypto API compliance test suite. +Clone the repo and check out the commit specified by PSA_ARCH_TEST_REPO and PSA_ARCH_TEST_REF, +then compile and run the test suite. The clone is stored at /psa-arch-tests. +Known defects in either the test suite or mbedtls / TF-PSA-Crypto - identified by their test +number - are ignored, while unexpected failures AND successes are reported as errors, to help +keep the list of known defects as up to date as possible. +""" + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +import argparse +import os +import re +import shutil +import subprocess +import sys +from typing import List +from pathlib import Path + +from mbedtls_framework import build_tree + +# PSA Compliance tests we expect to fail due to known defects in Mbed TLS / +# TF-PSA-Crypto (or the test suite). +# The test numbers correspond to the numbers used by the console output of the test suite. +# Test number 2xx corresponds to the files in the folder +# psa-arch-tests/api-tests/dev_apis/crypto/test_c0xx +EXPECTED_FAILURES = {} # type: dict + +PSA_ARCH_TESTS_REPO = 'https://github.com/ARM-software/psa-arch-tests.git' +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']) + + if build_tree.is_mbedtls_3_6(): + libraries_to_link = [str(install_dir.joinpath("lib/libmbedcrypto.a"))] + else: + 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) + try: + os.chdir(psa_arch_tests_dir) + + # Reuse existing local clone + subprocess.check_call(['git', 'init']) + subprocess.check_call(['git', 'fetch', PSA_ARCH_TESTS_REPO, PSA_ARCH_TESTS_REF]) + subprocess.check_call(['git', 'checkout', 'FETCH_HEAD']) + + build_dir = 'api-tests/build' + try: + shutil.rmtree(build_dir) + except FileNotFoundError: + pass + os.mkdir(build_dir) + os.chdir(build_dir) + + #pylint: disable=bad-continuation + subprocess.check_call([ + 'cmake', '..', + '-GUnix Makefiles', + '-DTARGET=tgt_dev_apis_stdc', + '-DTOOLCHAIN=HOST_GCC', + '-DSUITE=CRYPTO', + '-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'], + bufsize=1, stdout=subprocess.PIPE, universal_newlines=True) + + test_re = re.compile( + '^TEST: (?P[0-9]*)|' + '^TEST RESULT: (?PFAILED|PASSED)' + ) + test = -1 + unexpected_successes = set(EXPECTED_FAILURES) + expected_failures = [] # type: List[int] + unexpected_failures = [] # type: List[int] + if proc.stdout is None: + return 1 + + for line in proc.stdout: + print(line, end='') + match = test_re.match(line) + if match is not None: + groupdict = match.groupdict() + test_num = groupdict['test_num'] + if test_num is not None: + test = int(test_num) + elif groupdict['test_result'] == 'FAILED': + try: + unexpected_successes.remove(test) + expected_failures.append(test) + print('Expected failure, ignoring') + except KeyError: + unexpected_failures.append(test) + print('ERROR: Unexpected failure') + elif test in unexpected_successes: + print('ERROR: Unexpected success') + proc.wait() + + print() + print('***** test_psa_compliance.py report ******') + print() + print('Expected failures:', ', '.join(str(i) for i in expected_failures)) + print('Unexpected failures:', ', '.join(str(i) for i in unexpected_failures)) + print('Unexpected successes:', ', '.join(str(i) for i in sorted(unexpected_successes))) + print() + if unexpected_successes or unexpected_failures: + if unexpected_successes: + print('Unexpected successes encountered.') + print('Please remove the corresponding tests from ' + 'EXPECTED_FAILURES in tests/scripts/compliance_test.py') + print() + print('FAILED') + return 1 + else: + print('SUCCESS') + return 0 + finally: + os.chdir(root_dir) + +if __name__ == '__main__': + BUILD_DIR = 'out_of_source_build' + + # pylint: disable=invalid-name + parser = argparse.ArgumentParser() + parser.add_argument('--build-dir', nargs=1, + help='path to Mbed TLS / TF-PSA-Crypto build directory') + args = parser.parse_args() + + if args.build_dir is not None: + BUILD_DIR = args.build_dir[0] + + sys.exit(main(BUILD_DIR)) diff --git a/scripts/test_psa_constant_names.py b/scripts/test_psa_constant_names.py new file mode 100755 index 000000000..ad1311041 --- /dev/null +++ b/scripts/test_psa_constant_names.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +"""Test the program psa_constant_names. +Gather constant names from header files and test cases. Compile a C program +to print out their numerical values, feed these numerical values to +psa_constant_names, and check that the output is the original name. +Return 0 if all test cases pass, 1 if the output was not always as expected, +or 1 (with a Python backtrace) if there was an operational error. +""" + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +import argparse +from collections import namedtuple +import os +import re +import subprocess +import sys +from typing import Iterable, List, Optional, Tuple + +from mbedtls_framework import build_tree +from mbedtls_framework import c_build_helper +from mbedtls_framework.macro_collector import InputsForTest, PSAMacroEnumerator +from mbedtls_framework import typing_util + +def gather_inputs(headers: Iterable[str], + test_suites: Iterable[str], + inputs_class=InputsForTest) -> PSAMacroEnumerator: + """Read the list of inputs to test psa_constant_names with.""" + inputs = inputs_class() + for header in headers: + inputs.parse_header(header) + for test_cases in test_suites: + inputs.parse_test_cases(test_cases) + inputs.add_numerical_values() + inputs.gather_arguments() + return inputs + +def run_c(type_word: str, + expressions: Iterable[str], + include_path: Optional[str] = None, + keep_c: bool = False) -> List[str]: + """Generate and run a program to print out numerical values of C expressions.""" + if type_word == 'status': + cast_to = 'long' + printf_format = '%ld' + else: + cast_to = 'unsigned long' + printf_format = '0x%08lx' + return c_build_helper.get_c_expression_values( + cast_to, printf_format, + expressions, + caller='test_psa_constant_names.py for {} values'.format(type_word), + file_label=type_word, + header='#include ', + include_path=include_path, + keep_c=keep_c + ) + +NORMALIZE_STRIP_RE = re.compile(r'\s+') +def normalize(expr: str) -> str: + """Normalize the C expression so as not to care about trivial differences. + + Currently "trivial differences" means whitespace. + """ + return re.sub(NORMALIZE_STRIP_RE, '', expr) + +ALG_TRUNCATED_TO_SELF_RE = \ + re.compile(r'PSA_ALG_AEAD_WITH_SHORTENED_TAG\(' + r'PSA_ALG_(?:CCM|CHACHA20_POLY1305|GCM)' + r', *16\)\Z') + +def is_simplifiable(expr: str) -> bool: + """Determine whether an expression is simplifiable. + + Simplifiable expressions can't be output in their input form, since + the output will be the simple form. Therefore they must be excluded + from testing. + """ + if ALG_TRUNCATED_TO_SELF_RE.match(expr): + return True + return False + +def collect_values(inputs: InputsForTest, + type_word: str, + include_path: Optional[str] = None, + keep_c: bool = False) -> Tuple[List[str], List[str]]: + """Generate expressions using known macro names and calculate their values. + + Return a list of pairs of (expr, value) where expr is an expression and + value is a string representation of its integer value. + """ + names = inputs.get_names(type_word) + expressions = sorted(expr + for expr in inputs.generate_expressions(names) + if not is_simplifiable(expr)) + values = run_c(type_word, expressions, + include_path=include_path, keep_c=keep_c) + return expressions, values + +class Tests: + """An object representing tests and their results.""" + + Error = namedtuple('Error', + ['type', 'expression', 'value', 'output']) + + def __init__(self, options) -> None: + self.options = options + self.count = 0 + self.errors = [] #type: List[Tests.Error] + + def run_one(self, inputs: InputsForTest, type_word: str) -> None: + """Test psa_constant_names for the specified type. + + Run the program on the names for this type. + Use the inputs to figure out what arguments to pass to macros that + take arguments. + """ + expressions, values = collect_values(inputs, type_word, + include_path=self.options.include, + keep_c=self.options.keep_c) + output_bytes = subprocess.check_output([self.options.program, + type_word] + values) + output = output_bytes.decode('ascii') + outputs = output.strip().split('\n') + self.count += len(expressions) + for expr, value, output in zip(expressions, values, outputs): + if self.options.show: + sys.stdout.write('{} {}\t{}\n'.format(type_word, value, output)) + if normalize(expr) != normalize(output): + self.errors.append(self.Error(type=type_word, + expression=expr, + value=value, + output=output)) + + def run_all(self, inputs: InputsForTest) -> None: + """Run psa_constant_names on all the gathered inputs.""" + for type_word in ['status', 'algorithm', 'ecc_curve', 'dh_group', + 'key_type', 'key_usage']: + self.run_one(inputs, type_word) + + def report(self, out: typing_util.Writable) -> None: + """Describe each case where the output is not as expected. + + Write the errors to ``out``. + Also write a total. + """ + for error in self.errors: + out.write('For {} "{}", got "{}" (value: {})\n' + .format(error.type, error.expression, + error.output, error.value)) + out.write('{} test cases'.format(self.count)) + if self.errors: + out.write(', {} FAIL\n'.format(len(self.errors))) + else: + out.write(' PASS\n') + +HEADERS = ['psa/crypto.h', 'psa/crypto_extra.h', 'psa/crypto_values.h'] + +if build_tree.is_mbedtls_3_6(): + TEST_SUITES = ['tests/suites/test_suite_psa_crypto_metadata.data'] +else: + TEST_SUITES = ['tf-psa-crypto/tests/suites/test_suite_psa_crypto_metadata.data'] + +def main(): + parser = argparse.ArgumentParser(description=globals()['__doc__']) + if build_tree.is_mbedtls_3_6(): + parser.add_argument('--include', '-I', + action='append', default=['include'], + help='Directory for header files') + else: + parser.add_argument('--include', '-I', + action='append', default=['tf-psa-crypto/include', + 'tf-psa-crypto/drivers/builtin/include', + 'tf-psa-crypto/drivers/everest/include', + 'include'], + help='Directory for header files') + parser.add_argument('--keep-c', + action='store_true', dest='keep_c', default=False, + help='Keep the intermediate C file') + parser.add_argument('--no-keep-c', + action='store_false', dest='keep_c', + help='Don\'t keep the intermediate C file (default)') + if build_tree.is_mbedtls_3_6(): + parser.add_argument('--program', + default='programs/psa/psa_constant_names', + help='Program to test') + else: + parser.add_argument('--program', + default='tf-psa-crypto/programs/psa/psa_constant_names', + help='Program to test') + parser.add_argument('--show', + action='store_true', + help='Show tested values on stdout') + parser.add_argument('--no-show', + action='store_false', dest='show', + help='Don\'t show tested values (default)') + options = parser.parse_args() + headers = [os.path.join(options.include[0], h) for h in HEADERS] + inputs = gather_inputs(headers, TEST_SUITES) + tests = Tests(options) + tests.run_all(inputs) + tests.report(sys.stdout) + if tests.errors: + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/tests/programs/dlopen_demo.sh b/tests/programs/dlopen_demo.sh new file mode 100755 index 000000000..5b3135853 --- /dev/null +++ b/tests/programs/dlopen_demo.sh @@ -0,0 +1,56 @@ +#!/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 + +DEMO_COMMON_NEED_QUERY_COMPILE_TIME_CONFIG=0 + +SCRIPT_DIR=$(dirname "$0") +. "${SCRIPT_DIR}/../../scripts/project_detection.sh" +. "${SCRIPT_DIR}/../../scripts/demo_common.sh" + +msg "Test the dynamic loading of libmbed*" + +# Once demo_common.sh is sourced we'll have the following variables set: +# - $root_dir points to the root path of Mbed TLS or TF-PSA-Crypto; +# - $programs_dir points to "$root_dir/programs" folder. +if is_mbedtls_root $root_dir; then + msg "Running in Mbed TLS repo" + program="$programs_dir/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 */