mirror of
				https://github.com/Mbed-TLS/mbedtls.git
				synced 2025-10-30 10:45:34 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			694 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			694 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| 
 | |
| # Copyright The Mbed TLS Contributors
 | |
| # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
 | |
| 
 | |
| """
 | |
| Test Mbed TLS with a subset of algorithms.
 | |
| 
 | |
| This script can be divided into several steps:
 | |
| 
 | |
| First, include/mbedtls/mbedtls_config.h or a different config file passed
 | |
| in the arguments is parsed to extract any configuration options (using config.py).
 | |
| 
 | |
| Then, test domains (groups of jobs, tests) are built based on predefined data
 | |
| collected in the DomainData class. Here, each domain has five major traits:
 | |
| - domain name, can be used to run only specific tests via command-line;
 | |
| - configuration building method, described in detail below;
 | |
| - list of symbols passed to the configuration building method;
 | |
| - commands to be run on each job (only build, build and test, or any other custom);
 | |
| - optional list of symbols to be excluded from testing.
 | |
| 
 | |
| The configuration building method can be one of the three following:
 | |
| 
 | |
| - ComplementaryDomain - build a job for each passed symbol by disabling a single
 | |
|   symbol and its reverse dependencies (defined in REVERSE_DEPENDENCIES);
 | |
| 
 | |
| - ExclusiveDomain - build a job where, for each passed symbol, only this particular
 | |
|   one is defined and other symbols from the list are unset. For each job look for
 | |
|   any non-standard symbols to set/unset in EXCLUSIVE_GROUPS. These are usually not
 | |
|   direct dependencies, but rather non-trivial results of other configs missing. Then
 | |
|   look for any unset symbols and handle their reverse dependencies.
 | |
|   Examples of EXCLUSIVE_GROUPS usage:
 | |
|   - MBEDTLS_SHA512_C job turns off all hashes except SHA512. MBEDTLS_SSL_COOKIE_C
 | |
|     requires either SHA256 or SHA384 to work, so it also has to be disabled.
 | |
|     This is not a dependency on SHA512_C, but a result of an exclusive domain
 | |
|     config building method. Relevant field:
 | |
|     'MBEDTLS_SHA512_C': ['-MBEDTLS_SSL_COOKIE_C'],
 | |
| 
 | |
| - DualDomain - combination of the two above - both complementary and exclusive domain
 | |
|   job generation code will be run. Currently only used for hashes.
 | |
| 
 | |
| Lastly, the collected jobs are executed and (optionally) tested, with
 | |
| error reporting and coloring as configured in options. Each test starts with
 | |
| a full config without a couple of slowing down or unnecessary options
 | |
| (see set_reference_config), then the specific job config is derived.
 | |
| """
 | |
| import argparse
 | |
| import os
 | |
| import re
 | |
| import subprocess
 | |
| import sys
 | |
| import traceback
 | |
| from typing import Union
 | |
| 
 | |
| # Add the Mbed TLS Python library directory to the module search path
 | |
| import scripts_path # pylint: disable=unused-import
 | |
| import config
 | |
| from mbedtls_framework import c_build_helper
 | |
| from mbedtls_framework import crypto_knowledge
 | |
| from mbedtls_framework import psa_information
 | |
| 
 | |
| class Colors: # pylint: disable=too-few-public-methods
 | |
|     """Minimalistic support for colored output.
 | |
| Each field of an object of this class is either None if colored output
 | |
| is not possible or not desired, or a pair of strings (start, stop) such
 | |
| that outputting start switches the text color to the desired color and
 | |
| stop switches the text color back to the default."""
 | |
|     red = None
 | |
|     green = None
 | |
|     cyan = None
 | |
|     bold_red = None
 | |
|     bold_green = None
 | |
|     def __init__(self, options=None):
 | |
|         """Initialize color profile according to passed options."""
 | |
|         if not options or options.color in ['no', 'never']:
 | |
|             want_color = False
 | |
|         elif options.color in ['yes', 'always']:
 | |
|             want_color = True
 | |
|         else:
 | |
|             want_color = sys.stderr.isatty()
 | |
|         if want_color:
 | |
|             # Assume ANSI compatible terminal
 | |
|             normal = '\033[0m'
 | |
|             self.red = ('\033[31m', normal)
 | |
|             self.green = ('\033[32m', normal)
 | |
|             self.cyan = ('\033[36m', normal)
 | |
|             self.bold_red = ('\033[1;31m', normal)
 | |
|             self.bold_green = ('\033[1;32m', normal)
 | |
| NO_COLORS = Colors(None)
 | |
| 
 | |
| def log_line(text, prefix='depends.py:', suffix='', color=None):
 | |
|     """Print a status message."""
 | |
|     if color is not None:
 | |
|         prefix = color[0] + prefix
 | |
|         suffix = suffix + color[1]
 | |
|     sys.stderr.write(prefix + ' ' + text + suffix + '\n')
 | |
|     sys.stderr.flush()
 | |
| 
 | |
| def log_command(cmd):
 | |
|     """Print a trace of the specified command.
 | |
| cmd is a list of strings: a command name and its arguments."""
 | |
|     log_line(' '.join(cmd), prefix='+')
 | |
| 
 | |
| def option_exists(conf, option):
 | |
|     return option in conf.settings
 | |
| 
 | |
| def set_config_option_value(conf, option, colors, value: Union[bool, str]):
 | |
|     """Set/unset a configuration option, optionally specifying a value.
 | |
| value can be either True/False (set/unset config option), or a string,
 | |
| which will make a symbol defined with a certain value."""
 | |
|     if not option_exists(conf, option):
 | |
|         log_line('Symbol {} was not found in {}'.format(option, conf.filename), color=colors.red)
 | |
|         return False
 | |
| 
 | |
|     if value is False:
 | |
|         log_command(['config.py', 'unset', option])
 | |
|         conf.unset(option)
 | |
|     elif value is True:
 | |
|         log_command(['config.py', 'set', option])
 | |
|         conf.set(option)
 | |
|     else:
 | |
|         log_command(['config.py', 'set', option, value])
 | |
|         conf.set(option, value)
 | |
|     return True
 | |
| 
 | |
| def set_reference_config(conf, colors):
 | |
|     """Change the library configuration file (mbedtls_config.h) to the reference state.
 | |
| The reference state is the one from which the tested configurations are
 | |
| derived."""
 | |
|     # Turn off options that are not relevant to the tests and slow them down.
 | |
|     log_command(['config.py', 'full'])
 | |
|     conf.adapt(config.full_adapter)
 | |
|     set_config_option_value(conf, 'MBEDTLS_TEST_HOOKS', colors, False)
 | |
| 
 | |
| class Job:
 | |
|     """A job builds the library in a specific configuration and runs some tests."""
 | |
|     def __init__(self, name, config_settings, commands):
 | |
|         """Build a job object.
 | |
| The job uses the configuration described by config_settings. This is a
 | |
| dictionary where the keys are preprocessor symbols and the values are
 | |
| booleans or strings. A boolean indicates whether or not to #define the
 | |
| symbol. With a string, the symbol is #define'd to that value.
 | |
| After setting the configuration, the job runs the programs specified by
 | |
| commands. This is a list of lists of strings; each list of string is a
 | |
| command name and its arguments and is passed to subprocess.call with
 | |
| shell=False."""
 | |
|         self.name = name
 | |
|         self.config_settings = config_settings
 | |
|         self.commands = commands
 | |
| 
 | |
|     def announce(self, colors, what):
 | |
|         '''Announce the start or completion of a job.
 | |
| If what is None, announce the start of the job.
 | |
| If what is True, announce that the job has passed.
 | |
| If what is False, announce that the job has failed.'''
 | |
|         if what is True:
 | |
|             log_line(self.name + ' PASSED', color=colors.green)
 | |
|         elif what is False:
 | |
|             log_line(self.name + ' FAILED', color=colors.red)
 | |
|         else:
 | |
|             log_line('starting ' + self.name, color=colors.cyan)
 | |
| 
 | |
|     def configure(self, conf, colors):
 | |
|         '''Set library configuration options as required for the job.'''
 | |
|         set_reference_config(conf, colors)
 | |
|         for key, value in sorted(self.config_settings.items()):
 | |
|             ret = set_config_option_value(conf, key, colors, value)
 | |
|             if ret is False:
 | |
|                 return False
 | |
|         return True
 | |
| 
 | |
|     def _consistency_check(self):
 | |
|         '''Check if the testable option is consistent with the goal.
 | |
| 
 | |
|         The purpose of this function to ensure that every option is set or unset according to
 | |
|         the settings.
 | |
|         '''
 | |
|         log_command(['consistency check'])
 | |
|         c_name = None
 | |
|         exe_name = None
 | |
|         header = '#include "mbedtls/build_info.h"\n'
 | |
| 
 | |
|         # Generate a C error directive for each setting to test if it is active
 | |
|         for option, value in sorted(self.config_settings.items()):
 | |
|             header += '#if '
 | |
|             if value:
 | |
|                 header += '!'
 | |
|             header += f'defined({option})\n'
 | |
|             header += f'#error "{option}"\n'
 | |
|             header += '#endif\n'
 | |
|         include_path = ['include', 'tf-psa-crypto/include',
 | |
|                         'tf-psa-crypto/drivers/builtin/include']
 | |
| 
 | |
|         try:
 | |
|             # Generate a C file, build and run it
 | |
|             c_file, c_name, exe_name = c_build_helper.create_c_file(self.name)
 | |
|             c_build_helper.generate_c_file(c_file, 'depends.py', header, lambda x: '')
 | |
|             c_file.close()
 | |
|             c_build_helper.compile_c_file(c_name, exe_name, include_path)
 | |
|             return True
 | |
| 
 | |
|         except c_build_helper.CompileError as e:
 | |
|             # Read the command line output to find out which setting has been failed
 | |
|             failed = {m.group(1) for m in re.finditer('.*#error "(.*)"', e.message) if m}
 | |
|             log_line('Inconsistent config option(s):')
 | |
|             for option in sorted(failed):
 | |
|                 log_line('  ' + option)
 | |
|             return False
 | |
| 
 | |
|         finally:
 | |
|             c_build_helper.remove_file_if_exists(c_name)
 | |
|             c_build_helper.remove_file_if_exists(exe_name)
 | |
| 
 | |
|     def test(self, options):
 | |
|         '''Run the job's build and test commands.
 | |
| Return True if all the commands succeed and False otherwise.
 | |
| If options.keep_going is false, stop as soon as one command fails. Otherwise
 | |
| run all the commands, except that if the first command fails, none of the
 | |
| other commands are run (typically, the first command is a build command
 | |
| and subsequent commands are tests that cannot run if the build failed).'''
 | |
|         if not self._consistency_check():
 | |
|             return False
 | |
|         built = False
 | |
|         success = True
 | |
|         for command in self.commands:
 | |
|             log_command(command)
 | |
|             env = os.environ.copy()
 | |
|             if 'MBEDTLS_TEST_CONFIGURATION' in env:
 | |
|                 env['MBEDTLS_TEST_CONFIGURATION'] += '-' + self.name
 | |
|             ret = subprocess.call(command, env=env)
 | |
|             if ret != 0:
 | |
|                 if command[0] not in ['make', options.make_command]:
 | |
|                     log_line('*** [{}] Error {}'.format(' '.join(command), ret))
 | |
|                 if not options.keep_going or not built:
 | |
|                     return False
 | |
|                 success = False
 | |
|             built = True
 | |
|         return success
 | |
| 
 | |
| # If the configuration option A requires B, make sure that
 | |
| # B in REVERSE_DEPENDENCIES[A].
 | |
| # All the information here should be contained in check_config.h or check_crypto_config.h.
 | |
| # This file includes a copy because it changes rarely and it would be a pain
 | |
| # to extract automatically.
 | |
| REVERSE_DEPENDENCIES = {
 | |
|     'PSA_WANT_KEY_TYPE_AES': ['PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128',
 | |
|                               'MBEDTLS_CTR_DRBG_C',
 | |
|                               'MBEDTLS_NIST_KW_C',
 | |
|                               'MBEDTLS_AES_C'],
 | |
|     'PSA_WANT_KEY_TYPE_ARIA': ['MBEDTLS_ARIA_C'],
 | |
|     'PSA_WANT_KEY_TYPE_CAMELLIA': ['MBEDTLS_CAMELLIA_C'],
 | |
|     'PSA_WANT_KEY_TYPE_CHACHA20': ['PSA_WANT_ALG_CHACHA20_POLY1305',
 | |
|                                    'PSA_WANT_ALG_STREAM_CIPHER',
 | |
|                                    'MBEDTLS_CHACHA20_C',
 | |
|                                    'MBEDTLS_CHACHAPOLY_C'],
 | |
|     'PSA_WANT_KEY_TYPE_DES': ['MBEDTLS_DES_C'],
 | |
|     'PSA_WANT_ALG_CCM': ['PSA_WANT_ALG_CCM_STAR_NO_TAG',
 | |
|                          'MBEDTLS_CCM_C'],
 | |
|     'PSA_WANT_ALG_CMAC': ['PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128',
 | |
|                           'MBEDTLS_CMAC_C'],
 | |
|     'PSA_WANT_ALG_GCM': ['MBEDTLS_GCM_C'],
 | |
| 
 | |
|     'PSA_WANT_ALG_CBC_NO_PADDING': ['MBEDTLS_CIPHER_MODE_CBC'],
 | |
|     'PSA_WANT_ALG_CBC_PKCS7': ['MBEDTLS_CIPHER_MODE_CBC'],
 | |
|     'PSA_WANT_ALG_CFB': ['MBEDTLS_CIPHER_MODE_CFB'],
 | |
|     'PSA_WANT_ALG_CTR': ['MBEDTLS_CIPHER_MODE_CTR'],
 | |
|     'PSA_WANT_ALG_OFB': ['MBEDTLS_CIPHER_MODE_OFB'],
 | |
|     'PSA_WANT_ALG_XTS': ['MBEDTLS_CIPHER_MODE_XTS'],
 | |
| 
 | |
|     'MBEDTLS_CIPHER_PADDING_PKCS7': ['MBEDTLS_PKCS5_C',
 | |
|                                      'MBEDTLS_PKCS12_C',
 | |
|                                      'PSA_WANT_ALG_CBC_PKCS7'],
 | |
|     'MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS': ['MBEDTLS_CIPHER_MODE_CBC'],
 | |
|     'MBEDTLS_CIPHER_PADDING_ZEROS': ['MBEDTLS_CIPHER_MODE_CBC'],
 | |
|     'MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN': ['MBEDTLS_CIPHER_MODE_CBC'],
 | |
| 
 | |
|     'PSA_WANT_ECC_BRAINPOOL_P_R1_256': ['MBEDTLS_ECP_DP_BP256R1_ENABLED'],
 | |
|     'PSA_WANT_ECC_BRAINPOOL_P_R1_384': ['MBEDTLS_ECP_DP_BP384R1_ENABLED'],
 | |
|     'PSA_WANT_ECC_BRAINPOOL_P_R1_512': ['MBEDTLS_ECP_DP_BP512R1_ENABLED'],
 | |
|     'PSA_WANT_ECC_MONTGOMERY_255': ['MBEDTLS_ECP_DP_CURVE25519_ENABLED'],
 | |
|     'PSA_WANT_ECC_MONTGOMERY_448': ['MBEDTLS_ECP_DP_CURVE448_ENABLED'],
 | |
|     'PSA_WANT_ECC_SECP_R1_192': ['MBEDTLS_ECP_DP_SECP192R1_ENABLED'],
 | |
|     'PSA_WANT_ECC_SECP_R1_224': ['MBEDTLS_ECP_DP_SECP224R1_ENABLED'],
 | |
|     'PSA_WANT_ECC_SECP_R1_256': ['MBEDTLS_ECJPAKE_C',
 | |
|                                  'MBEDTLS_ECP_DP_SECP256R1_ENABLED'],
 | |
|     'PSA_WANT_ECC_SECP_R1_384': ['MBEDTLS_ECP_DP_SECP384R1_ENABLED'],
 | |
|     'PSA_WANT_ECC_SECP_R1_521': ['MBEDTLS_ECP_DP_SECP521R1_ENABLED'],
 | |
|     'PSA_WANT_ECC_SECP_K1_192': ['MBEDTLS_ECP_DP_SECP192K1_ENABLED'],
 | |
|     'PSA_WANT_ECC_SECP_K1_256': ['MBEDTLS_ECP_DP_SECP256K1_ENABLED'],
 | |
| 
 | |
|     'MBEDTLS_ECDSA_C': ['MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED',
 | |
|                         'MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED',
 | |
|                         'PSA_WANT_ALG_ECDSA',
 | |
|                         'PSA_WANT_ALG_DETERMINISTIC_ECDSA'],
 | |
|     'MBEDTLS_ECP_C': ['MBEDTLS_ECDSA_C',
 | |
|                       'MBEDTLS_ECDH_C', 'PSA_WANT_ALG_ECDH',
 | |
|                       'MBEDTLS_ECJPAKE_C',
 | |
|                       'MBEDTLS_ECP_RESTARTABLE',
 | |
|                       'MBEDTLS_PK_PARSE_EC_EXTENDED',
 | |
|                       'MBEDTLS_PK_PARSE_EC_COMPRESSED',
 | |
|                       'MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED',
 | |
|                       'MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED',
 | |
|                       'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED',
 | |
|                       'MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED',
 | |
|                       'MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED',
 | |
|                       'PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY',
 | |
|                       'PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_BASIC',
 | |
|                       'PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT',
 | |
|                       'PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT',
 | |
|                       'PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_GENERATE',
 | |
|                       'PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_DERIVE'],
 | |
|     'MBEDTLS_ECJPAKE_C': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED',
 | |
|                           'PSA_WANT_ALG_JPAKE'],
 | |
|     'MBEDTLS_PKCS1_V21': ['MBEDTLS_X509_RSASSA_PSS_SUPPORT',
 | |
|                           'PSA_WANT_ALG_RSA_OAEP',
 | |
|                           'PSA_WANT_ALG_RSA_PSS'],
 | |
|     'MBEDTLS_PKCS1_V15': ['MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED',
 | |
|                           'MBEDTLS_KEY_EXCHANGE_RSA_ENABLED',
 | |
|                           'PSA_WANT_ALG_RSA_PKCS1V15_CRYPT',
 | |
|                           'PSA_WANT_ALG_RSA_PKCS1V15_SIGN'],
 | |
|     'MBEDTLS_RSA_C': ['MBEDTLS_PKCS1_V15',
 | |
|                       'MBEDTLS_PKCS1_V21',
 | |
|                       'MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED',
 | |
|                       'PSA_WANT_KEY_TYPE_RSA_PUBLIC_KEY',
 | |
|                       'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC',
 | |
|                       'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_IMPORT',
 | |
|                       'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_EXPORT',
 | |
|                       'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE'],
 | |
| 
 | |
|     'MBEDTLS_MD5_C' : ['PSA_WANT_ALG_MD5'],
 | |
|     'MBEDTLS_RIPEMD160_C' : ['PSA_WANT_ALG_RIPEMD160'],
 | |
|     'MBEDTLS_SHA1_C' : ['PSA_WANT_ALG_SHA_1'],
 | |
|     'MBEDTLS_SHA224_C': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED',
 | |
|                          'MBEDTLS_ENTROPY_FORCE_SHA256',
 | |
|                          'MBEDTLS_SHA256_USE_ARMV8_A_CRYPTO_IF_PRESENT',
 | |
|                          'MBEDTLS_SHA256_USE_ARMV8_A_CRYPTO_ONLY',
 | |
|                          'PSA_WANT_ALG_SHA_224'],
 | |
|     'MBEDTLS_SHA256_C': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED',
 | |
|                          'MBEDTLS_ENTROPY_FORCE_SHA256',
 | |
|                          'MBEDTLS_SHA256_USE_ARMV8_A_CRYPTO_IF_PRESENT',
 | |
|                          'MBEDTLS_SHA256_USE_ARMV8_A_CRYPTO_ONLY',
 | |
|                          'MBEDTLS_LMS_C',
 | |
|                          'MBEDTLS_LMS_PRIVATE',
 | |
|                          'PSA_WANT_ALG_SHA_256',
 | |
|                          'PSA_WANT_ALG_TLS12_ECJPAKE_TO_PMS'],
 | |
|     'MBEDTLS_SHA384_C' : ['PSA_WANT_ALG_SHA_384'],
 | |
|     'MBEDTLS_SHA512_C': ['MBEDTLS_SHA512_USE_A64_CRYPTO_IF_PRESENT',
 | |
|                          'MBEDTLS_SHA512_USE_A64_CRYPTO_ONLY',
 | |
|                          'PSA_WANT_ALG_SHA_512'],
 | |
|     'MBEDTLS_SHA3_C' : ['PSA_WANT_ALG_SHA3_224',
 | |
|                         'PSA_WANT_ALG_SHA3_256',
 | |
|                         'PSA_WANT_ALG_SHA3_384',
 | |
|                         'PSA_WANT_ALG_SHA3_512'],
 | |
| }
 | |
| 
 | |
| # If an option is tested in an exclusive test, alter the following defines.
 | |
| # These are not necessarily dependencies, but just minimal required changes
 | |
| # if a given define is the only one enabled from an exclusive group.
 | |
| EXCLUSIVE_GROUPS = {
 | |
|     'MBEDTLS_SHA512_C': ['-MBEDTLS_SSL_COOKIE_C',
 | |
|                          '-MBEDTLS_SSL_TLS_C'],
 | |
|     'PSA_WANT_ECC_MONTGOMERY_448': ['-MBEDTLS_ECDSA_C',
 | |
|                                     '-MBEDTLS_ECDSA_DETERMINISTIC',
 | |
|                                     '-MBEDTLS_ECJPAKE_C',],
 | |
|     'PSA_WANT_ECC_MONTGOMERY_255': ['-MBEDTLS_ECDSA_C',
 | |
|                                     '-MBEDTLS_ECDSA_DETERMINISTIC',
 | |
|                                     '-MBEDTLS_ECJPAKE_C'],
 | |
|     'PSA_WANT_KEY_TYPE_ARIA': ['-PSA_WANT_ALG_CMAC',
 | |
|                                '-PSA_WANT_ALG_CCM',
 | |
|                                '-PSA_WANT_ALG_GCM',
 | |
|                                '-MBEDTLS_SSL_TICKET_C',
 | |
|                                '-MBEDTLS_SSL_CONTEXT_SERIALIZATION'],
 | |
|     'PSA_WANT_KEY_TYPE_CAMELLIA': ['-PSA_WANT_ALG_CMAC'],
 | |
|     'PSA_WANT_KEY_TYPE_CHACHA20': ['-PSA_WANT_ALG_CMAC',
 | |
|                                    '-PSA_WANT_ALG_CCM',
 | |
|                                    '-PSA_WANT_ALG_GCM',
 | |
|                                    '-PSA_WANT_ALG_ECB_NO_PADDING'],
 | |
|     'PSA_WANT_KEY_TYPE_DES': ['-PSA_WANT_ALG_CCM',
 | |
|                               '-PSA_WANT_ALG_GCM',
 | |
|                               '-MBEDTLS_SSL_TICKET_C',
 | |
|                               '-MBEDTLS_SSL_CONTEXT_SERIALIZATION'],
 | |
| }
 | |
| def handle_exclusive_groups(config_settings, symbol):
 | |
|     """For every symbol tested in an exclusive group check if there are other
 | |
| defines to be altered. """
 | |
|     for dep in EXCLUSIVE_GROUPS.get(symbol, []):
 | |
|         unset = dep.startswith('-')
 | |
|         dep = dep[1:]
 | |
|         config_settings[dep] = not unset
 | |
| 
 | |
| def turn_off_dependencies(config_settings, exclude=None):
 | |
|     """For every option turned off config_settings, also turn off what depends on it.
 | |
| 
 | |
|     An option O is turned off if config_settings[O] is False.
 | |
|     Handle the dependencies recursively.
 | |
| 
 | |
|     If 'exclude' is a symbol, ensure its dependencies are not turned off while dependencies
 | |
|     of other settings are turned off.
 | |
|     """
 | |
| 
 | |
|     # Determine recursively the settings that should not be turned off for the sake of 'exclude'.
 | |
|     excludes = set()
 | |
|     if exclude:
 | |
|         revdep = set(REVERSE_DEPENDENCIES.get(exclude, []))
 | |
|         while revdep:
 | |
|             dep = revdep.pop()
 | |
|             excludes.add(dep)
 | |
|             revdep.update(set(REVERSE_DEPENDENCIES.get(dep, [])) - excludes)
 | |
| 
 | |
|     for key, value in sorted(config_settings.items()):
 | |
|         if value is not False:
 | |
|             continue
 | |
| 
 | |
|         # Save the processed settings to handle cross referencies.
 | |
|         # Start with set of settings that we do not want to turn off.
 | |
|         history = excludes.copy()
 | |
|         revdep = set(REVERSE_DEPENDENCIES.get(key, [])) - excludes
 | |
|         while revdep:
 | |
|             dep = revdep.pop()
 | |
|             history.add(dep)
 | |
|             config_settings[dep] = False
 | |
|             # Do not add symbols which are already processed
 | |
|             revdep.update(set(REVERSE_DEPENDENCIES.get(dep, [])) - history)
 | |
| 
 | |
| class BaseDomain: # pylint: disable=too-few-public-methods, unused-argument
 | |
|     """A base class for all domains."""
 | |
|     def __init__(self, symbols, commands, exclude):
 | |
|         """Initialize the jobs container"""
 | |
|         self.jobs = []
 | |
| 
 | |
| class ExclusiveDomain(BaseDomain): # pylint: disable=too-few-public-methods
 | |
|     """A domain consisting of a set of conceptually-equivalent settings.
 | |
| Establish a list of configuration symbols. For each symbol, run a test job
 | |
| with this symbol set and the others unset."""
 | |
|     def __init__(self, symbols, commands, exclude=None):
 | |
|         """Build a domain for the specified list of configuration symbols.
 | |
| The domain contains a set of jobs that enable one of the elements
 | |
| of symbols and disable the others.
 | |
| Each job runs the specified commands.
 | |
| If exclude is a regular expression, skip generated jobs whose description
 | |
| would match this regular expression."""
 | |
|         super().__init__(symbols, commands, exclude)
 | |
|         base_config_settings = {}
 | |
|         for symbol in symbols:
 | |
|             base_config_settings[symbol] = False
 | |
|         for symbol in symbols:
 | |
|             description = symbol
 | |
|             if exclude and re.match(exclude, description):
 | |
|                 continue
 | |
|             config_settings = base_config_settings.copy()
 | |
|             config_settings[symbol] = True
 | |
|             handle_exclusive_groups(config_settings, symbol)
 | |
|             turn_off_dependencies(config_settings, symbol)
 | |
|             job = Job(description, config_settings, commands)
 | |
|             self.jobs.append(job)
 | |
| 
 | |
| class ComplementaryDomain(BaseDomain): # pylint: disable=too-few-public-methods
 | |
|     """A domain consisting of a set of loosely-related settings.
 | |
| Establish a list of configuration symbols. For each symbol, run a test job
 | |
| with this symbol unset.
 | |
| If exclude is a regular expression, skip generated jobs whose description
 | |
| would match this regular expression."""
 | |
|     def __init__(self, symbols, commands, exclude=None):
 | |
|         """Build a domain for the specified list of configuration symbols.
 | |
| Each job in the domain disables one of the specified symbols.
 | |
| Each job runs the specified commands."""
 | |
|         super().__init__(symbols, commands, exclude)
 | |
|         for symbol in symbols:
 | |
|             description = '!' + symbol
 | |
|             if exclude and re.match(exclude, description):
 | |
|                 continue
 | |
|             config_settings = {symbol: False}
 | |
|             turn_off_dependencies(config_settings)
 | |
|             job = Job(description, config_settings, commands)
 | |
|             self.jobs.append(job)
 | |
| 
 | |
| class DualDomain(ExclusiveDomain, ComplementaryDomain): # pylint: disable=too-few-public-methods
 | |
|     """A domain that contains both the ExclusiveDomain and BaseDomain tests.
 | |
| Both parent class __init__ calls are performed in any order and
 | |
| each call adds respective jobs. The job array initialization is done once in
 | |
| BaseDomain, before the parent __init__ calls."""
 | |
| 
 | |
| class DomainData:
 | |
|     """A container for domains and jobs, used to structurize testing."""
 | |
|     def config_symbols_matching(self, regexp):
 | |
|         """List the mbedtls_config.h settings matching regexp."""
 | |
|         return [symbol for symbol in self.all_config_symbols
 | |
|                 if re.match(regexp, symbol)]
 | |
| 
 | |
|     # pylint: disable=too-many-locals
 | |
|     def __init__(self, options, conf):
 | |
|         """Gather data about the library and establish a list of domains to test."""
 | |
|         build_command = [options.make_command, 'CFLAGS=-Werror -O2']
 | |
|         build_and_test = [build_command, [options.make_command, 'test']]
 | |
|         self.all_config_symbols = set(conf.settings.keys())
 | |
|         psa_info = psa_information.Information().constructors
 | |
|         algs = {crypto_knowledge.Algorithm(alg): symbol
 | |
|                 for alg, symbol in ((alg, psa_information.psa_want_symbol(alg))
 | |
|                                     for alg in psa_info.algorithms)
 | |
|                 if symbol in self.all_config_symbols}
 | |
|         cipher_algs = {alg
 | |
|                        for alg in algs
 | |
|                        if alg.can_do(crypto_knowledge.AlgorithmCategory.CIPHER)}
 | |
|         key_types = {crypto_knowledge.KeyType(expr): symbol
 | |
|                      for key_type in psa_info.key_types
 | |
|                      for expr, symbol in ((expr, psa_information.psa_want_symbol(key_type))
 | |
|                                           for expr in psa_info.generate_expressions([key_type]))
 | |
|                      if symbol in self.all_config_symbols}
 | |
| 
 | |
|         # Find hash modules by name.
 | |
|         hash_symbols = self.config_symbols_matching(r'MBEDTLS_(MD|RIPEMD|SHA)[0-9]+_C\Z')
 | |
| 
 | |
|         # Find elliptic curve enabling macros
 | |
|         # MBEDTLS_ECP_DP_SECP224K1_ENABLED added to disable it for all curves
 | |
|         curve_symbols = self.config_symbols_matching(r'PSA_WANT_ECC_\w+\Z|'
 | |
|                                                      r'MBEDTLS_ECP_DP_SECP224K1_ENABLED')
 | |
| 
 | |
|         # Find key exchange enabling macros by name.
 | |
|         key_exchange_symbols = self.config_symbols_matching(r'MBEDTLS_KEY_EXCHANGE_\w+_ENABLED\Z')
 | |
| 
 | |
|         # Find cipher key types
 | |
|         cipher_key_types = {symbol
 | |
|                             for key_type, symbol in key_types.items()
 | |
|                             for alg in cipher_algs
 | |
|                             if key_type.can_do(alg)}
 | |
| 
 | |
|         # Get cipher modes
 | |
|         cipher_chaining_symbols = {algs[cipher_alg] for cipher_alg in cipher_algs}
 | |
| 
 | |
|         # Find block padding mode enabling macros by name.
 | |
|         cipher_padding_symbols = self.config_symbols_matching(r'MBEDTLS_CIPHER_PADDING_\w+\Z')
 | |
| 
 | |
|         self.domains = {
 | |
|             # Cipher key types
 | |
|             'cipher_id': ExclusiveDomain(cipher_key_types, build_and_test),
 | |
| 
 | |
|             # XTS is not yet supported via the PSA API.
 | |
|             # See https://github.com/Mbed-TLS/mbedtls/issues/6384
 | |
|             'cipher_chaining': ExclusiveDomain(cipher_chaining_symbols,
 | |
|                                                build_and_test,
 | |
|                                                exclude=r'PSA_WANT_ALG_XTS'),
 | |
| 
 | |
|             'cipher_padding': ExclusiveDomain(cipher_padding_symbols,
 | |
|                                               build_and_test),
 | |
| 
 | |
|             # Elliptic curves. Run the test suites.
 | |
|             'curves': ExclusiveDomain(curve_symbols, build_and_test,
 | |
|                                       exclude=r'MBEDTLS_ECP_DP_SECP224K1_ENABLED'),
 | |
| 
 | |
|             # Hash algorithms. Excluding exclusive domains of MD, RIPEMD, SHA1,
 | |
|             # SHA224 and SHA384 because MBEDTLS_ENTROPY_C is extensively used
 | |
|             # across various modules, but it depends on either SHA256 or SHA512.
 | |
|             # As a consequence an "exclusive" test of anything other than SHA256
 | |
|             # or SHA512 with MBEDTLS_ENTROPY_C enabled is not possible.
 | |
|             'hashes': DualDomain(hash_symbols, build_and_test,
 | |
|                                  exclude=r'MBEDTLS_(MD|RIPEMD|SHA1_)' \
 | |
|                                           '|MBEDTLS_SHA224_' \
 | |
|                                           '|MBEDTLS_SHA384_' \
 | |
|                                           '|MBEDTLS_SHA3_'),
 | |
|             # Key exchange types.
 | |
|             'kex': ExclusiveDomain(key_exchange_symbols, build_and_test),
 | |
|             'pkalgs': ComplementaryDomain(['MBEDTLS_ECDSA_C',
 | |
|                                            'MBEDTLS_ECP_C',
 | |
|                                            'MBEDTLS_PKCS1_V21',
 | |
|                                            'MBEDTLS_PKCS1_V15',
 | |
|                                            'MBEDTLS_RSA_C',
 | |
|                                            'MBEDTLS_X509_RSASSA_PSS_SUPPORT'],
 | |
|                                           build_and_test),
 | |
|         }
 | |
|         self.jobs = {}
 | |
|         for domain in self.domains.values():
 | |
|             for job in domain.jobs:
 | |
|                 self.jobs[job.name] = job
 | |
| 
 | |
|     def get_jobs(self, name):
 | |
|         """Return the list of jobs identified by the given name.
 | |
| A name can either be the name of a domain or the name of one specific job."""
 | |
|         if name in self.domains:
 | |
|             return sorted(self.domains[name].jobs, key=lambda job: job.name)
 | |
|         else:
 | |
|             return [self.jobs[name]]
 | |
| 
 | |
| def run(options, job, conf, colors=NO_COLORS):
 | |
|     """Run the specified job (a Job instance)."""
 | |
|     subprocess.check_call([options.make_command, 'clean'])
 | |
|     job.announce(colors, None)
 | |
|     if not job.configure(conf, colors):
 | |
|         job.announce(colors, False)
 | |
|         return False
 | |
|     conf.write()
 | |
|     success = job.test(options)
 | |
|     job.announce(colors, success)
 | |
|     return success
 | |
| 
 | |
| def run_tests(options, domain_data, conf):
 | |
|     """Run the desired jobs.
 | |
| domain_data should be a DomainData instance that describes the available
 | |
| domains and jobs.
 | |
| Run the jobs listed in options.tasks."""
 | |
|     colors = Colors(options)
 | |
|     jobs = []
 | |
|     failures = []
 | |
|     successes = []
 | |
|     for name in options.tasks:
 | |
|         jobs += domain_data.get_jobs(name)
 | |
|     conf.backup()
 | |
|     try:
 | |
|         for job in jobs:
 | |
|             success = run(options, job, conf, colors=colors)
 | |
|             if not success:
 | |
|                 if options.keep_going:
 | |
|                     failures.append(job.name)
 | |
|                 else:
 | |
|                     return False
 | |
|             else:
 | |
|                 successes.append(job.name)
 | |
|         conf.restore()
 | |
|     except:
 | |
|         # Restore the configuration, except in stop-on-error mode if there
 | |
|         # was an error, where we leave the failing configuration up for
 | |
|         # developer convenience.
 | |
|         if options.keep_going:
 | |
|             conf.restore()
 | |
|         raise
 | |
|     if successes:
 | |
|         log_line('{} passed'.format(' '.join(successes)), color=colors.bold_green)
 | |
|     if failures:
 | |
|         log_line('{} FAILED'.format(' '.join(failures)), color=colors.bold_red)
 | |
|         return False
 | |
|     else:
 | |
|         return True
 | |
| 
 | |
| def main():
 | |
|     try:
 | |
|         parser = argparse.ArgumentParser(
 | |
|             formatter_class=argparse.RawDescriptionHelpFormatter,
 | |
|             description=
 | |
|             "Test Mbed TLS with a subset of algorithms.\n\n"
 | |
|             "Example usage:\n"
 | |
|             r"./tests/scripts/depends.py \!MBEDTLS_SHA1_C MBEDTLS_SHA256_C""\n"
 | |
|             "./tests/scripts/depends.py MBEDTLS_AES_C hashes\n"
 | |
|             "./tests/scripts/depends.py cipher_id cipher_chaining\n")
 | |
|         parser.add_argument('--color', metavar='WHEN',
 | |
|                             help='Colorize the output (always/auto/never)',
 | |
|                             choices=['always', 'auto', 'never'], default='auto')
 | |
|         parser.add_argument('-c', '--config', metavar='FILE',
 | |
|                             help='Configuration file to modify',
 | |
|                             default=config.MbedTLSConfigFile.default_path[0])
 | |
|         parser.add_argument('-r', '--crypto-config', metavar='FILE',
 | |
|                             help='Crypto configuration file to modify',
 | |
|                             default=config.CryptoConfigFile.default_path[0])
 | |
|         parser.add_argument('-C', '--directory', metavar='DIR',
 | |
|                             help='Change to this directory before anything else',
 | |
|                             default='.')
 | |
|         parser.add_argument('-k', '--keep-going',
 | |
|                             help='Try all configurations even if some fail (default)',
 | |
|                             action='store_true', dest='keep_going', default=True)
 | |
|         parser.add_argument('-e', '--no-keep-going',
 | |
|                             help='Stop as soon as a configuration fails',
 | |
|                             action='store_false', dest='keep_going')
 | |
|         parser.add_argument('--list-jobs',
 | |
|                             help='List supported jobs and exit',
 | |
|                             action='append_const', dest='list', const='jobs')
 | |
|         parser.add_argument('--list-domains',
 | |
|                             help='List supported domains and exit',
 | |
|                             action='append_const', dest='list', const='domains')
 | |
|         parser.add_argument('--make-command', metavar='CMD',
 | |
|                             help='Command to run instead of make (e.g. gmake)',
 | |
|                             action='store', default='make')
 | |
|         parser.add_argument('tasks', metavar='TASKS', nargs='*',
 | |
|                             help='The domain(s) or job(s) to test (default: all).',
 | |
|                             default=True)
 | |
|         options = parser.parse_args()
 | |
|         os.chdir(options.directory)
 | |
|         conf = config.CombinedConfig(config.MbedTLSConfigFile(options.config),
 | |
|                                      config.CryptoConfigFile(options.crypto_config))
 | |
|         domain_data = DomainData(options, conf)
 | |
| 
 | |
|         if options.tasks is True:
 | |
|             options.tasks = sorted(domain_data.domains.keys())
 | |
|         if options.list:
 | |
|             for arg in options.list:
 | |
|                 for domain_name in sorted(getattr(domain_data, arg).keys()):
 | |
|                     print(domain_name)
 | |
|             sys.exit(0)
 | |
|         else:
 | |
|             sys.exit(0 if run_tests(options, domain_data, conf) else 1)
 | |
|     except Exception: # pylint: disable=broad-except
 | |
|         traceback.print_exc()
 | |
|         sys.exit(3)
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |