1*62c56f98SSadaf Ebrahimi#!/usr/bin/env python3 2*62c56f98SSadaf Ebrahimi 3*62c56f98SSadaf Ebrahimi# Copyright The Mbed TLS Contributors 4*62c56f98SSadaf Ebrahimi# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 5*62c56f98SSadaf Ebrahimi 6*62c56f98SSadaf Ebrahimi""" 7*62c56f98SSadaf EbrahimiTest Mbed TLS with a subset of algorithms. 8*62c56f98SSadaf Ebrahimi 9*62c56f98SSadaf EbrahimiThis script can be divided into several steps: 10*62c56f98SSadaf Ebrahimi 11*62c56f98SSadaf EbrahimiFirst, include/mbedtls/mbedtls_config.h or a different config file passed 12*62c56f98SSadaf Ebrahimiin the arguments is parsed to extract any configuration options (using config.py). 13*62c56f98SSadaf Ebrahimi 14*62c56f98SSadaf EbrahimiThen, test domains (groups of jobs, tests) are built based on predefined data 15*62c56f98SSadaf Ebrahimicollected in the DomainData class. Here, each domain has five major traits: 16*62c56f98SSadaf Ebrahimi- domain name, can be used to run only specific tests via command-line; 17*62c56f98SSadaf Ebrahimi- configuration building method, described in detail below; 18*62c56f98SSadaf Ebrahimi- list of symbols passed to the configuration building method; 19*62c56f98SSadaf Ebrahimi- commands to be run on each job (only build, build and test, or any other custom); 20*62c56f98SSadaf Ebrahimi- optional list of symbols to be excluded from testing. 21*62c56f98SSadaf Ebrahimi 22*62c56f98SSadaf EbrahimiThe configuration building method can be one of the three following: 23*62c56f98SSadaf Ebrahimi 24*62c56f98SSadaf Ebrahimi- ComplementaryDomain - build a job for each passed symbol by disabling a single 25*62c56f98SSadaf Ebrahimi symbol and its reverse dependencies (defined in REVERSE_DEPENDENCIES); 26*62c56f98SSadaf Ebrahimi 27*62c56f98SSadaf Ebrahimi- ExclusiveDomain - build a job where, for each passed symbol, only this particular 28*62c56f98SSadaf Ebrahimi one is defined and other symbols from the list are unset. For each job look for 29*62c56f98SSadaf Ebrahimi any non-standard symbols to set/unset in EXCLUSIVE_GROUPS. These are usually not 30*62c56f98SSadaf Ebrahimi direct dependencies, but rather non-trivial results of other configs missing. Then 31*62c56f98SSadaf Ebrahimi look for any unset symbols and handle their reverse dependencies. 32*62c56f98SSadaf Ebrahimi Examples of EXCLUSIVE_GROUPS usage: 33*62c56f98SSadaf Ebrahimi - MBEDTLS_SHA512_C job turns off all hashes except SHA512. MBEDTLS_SSL_COOKIE_C 34*62c56f98SSadaf Ebrahimi requires either SHA256 or SHA384 to work, so it also has to be disabled. 35*62c56f98SSadaf Ebrahimi This is not a dependency on SHA512_C, but a result of an exclusive domain 36*62c56f98SSadaf Ebrahimi config building method. Relevant field: 37*62c56f98SSadaf Ebrahimi 'MBEDTLS_SHA512_C': ['-MBEDTLS_SSL_COOKIE_C'], 38*62c56f98SSadaf Ebrahimi 39*62c56f98SSadaf Ebrahimi- DualDomain - combination of the two above - both complementary and exclusive domain 40*62c56f98SSadaf Ebrahimi job generation code will be run. Currently only used for hashes. 41*62c56f98SSadaf Ebrahimi 42*62c56f98SSadaf EbrahimiLastly, the collected jobs are executed and (optionally) tested, with 43*62c56f98SSadaf Ebrahimierror reporting and coloring as configured in options. Each test starts with 44*62c56f98SSadaf Ebrahimia full config without a couple of slowing down or unnecessary options 45*62c56f98SSadaf Ebrahimi(see set_reference_config), then the specific job config is derived. 46*62c56f98SSadaf Ebrahimi""" 47*62c56f98SSadaf Ebrahimiimport argparse 48*62c56f98SSadaf Ebrahimiimport os 49*62c56f98SSadaf Ebrahimiimport re 50*62c56f98SSadaf Ebrahimiimport shutil 51*62c56f98SSadaf Ebrahimiimport subprocess 52*62c56f98SSadaf Ebrahimiimport sys 53*62c56f98SSadaf Ebrahimiimport traceback 54*62c56f98SSadaf Ebrahimifrom typing import Union 55*62c56f98SSadaf Ebrahimi 56*62c56f98SSadaf Ebrahimi# Add the Mbed TLS Python library directory to the module search path 57*62c56f98SSadaf Ebrahimiimport scripts_path # pylint: disable=unused-import 58*62c56f98SSadaf Ebrahimiimport config 59*62c56f98SSadaf Ebrahimi 60*62c56f98SSadaf Ebrahimiclass Colors: # pylint: disable=too-few-public-methods 61*62c56f98SSadaf Ebrahimi """Minimalistic support for colored output. 62*62c56f98SSadaf EbrahimiEach field of an object of this class is either None if colored output 63*62c56f98SSadaf Ebrahimiis not possible or not desired, or a pair of strings (start, stop) such 64*62c56f98SSadaf Ebrahimithat outputting start switches the text color to the desired color and 65*62c56f98SSadaf Ebrahimistop switches the text color back to the default.""" 66*62c56f98SSadaf Ebrahimi red = None 67*62c56f98SSadaf Ebrahimi green = None 68*62c56f98SSadaf Ebrahimi cyan = None 69*62c56f98SSadaf Ebrahimi bold_red = None 70*62c56f98SSadaf Ebrahimi bold_green = None 71*62c56f98SSadaf Ebrahimi def __init__(self, options=None): 72*62c56f98SSadaf Ebrahimi """Initialize color profile according to passed options.""" 73*62c56f98SSadaf Ebrahimi if not options or options.color in ['no', 'never']: 74*62c56f98SSadaf Ebrahimi want_color = False 75*62c56f98SSadaf Ebrahimi elif options.color in ['yes', 'always']: 76*62c56f98SSadaf Ebrahimi want_color = True 77*62c56f98SSadaf Ebrahimi else: 78*62c56f98SSadaf Ebrahimi want_color = sys.stderr.isatty() 79*62c56f98SSadaf Ebrahimi if want_color: 80*62c56f98SSadaf Ebrahimi # Assume ANSI compatible terminal 81*62c56f98SSadaf Ebrahimi normal = '\033[0m' 82*62c56f98SSadaf Ebrahimi self.red = ('\033[31m', normal) 83*62c56f98SSadaf Ebrahimi self.green = ('\033[32m', normal) 84*62c56f98SSadaf Ebrahimi self.cyan = ('\033[36m', normal) 85*62c56f98SSadaf Ebrahimi self.bold_red = ('\033[1;31m', normal) 86*62c56f98SSadaf Ebrahimi self.bold_green = ('\033[1;32m', normal) 87*62c56f98SSadaf EbrahimiNO_COLORS = Colors(None) 88*62c56f98SSadaf Ebrahimi 89*62c56f98SSadaf Ebrahimidef log_line(text, prefix='depends.py:', suffix='', color=None): 90*62c56f98SSadaf Ebrahimi """Print a status message.""" 91*62c56f98SSadaf Ebrahimi if color is not None: 92*62c56f98SSadaf Ebrahimi prefix = color[0] + prefix 93*62c56f98SSadaf Ebrahimi suffix = suffix + color[1] 94*62c56f98SSadaf Ebrahimi sys.stderr.write(prefix + ' ' + text + suffix + '\n') 95*62c56f98SSadaf Ebrahimi sys.stderr.flush() 96*62c56f98SSadaf Ebrahimi 97*62c56f98SSadaf Ebrahimidef log_command(cmd): 98*62c56f98SSadaf Ebrahimi """Print a trace of the specified command. 99*62c56f98SSadaf Ebrahimicmd is a list of strings: a command name and its arguments.""" 100*62c56f98SSadaf Ebrahimi log_line(' '.join(cmd), prefix='+') 101*62c56f98SSadaf Ebrahimi 102*62c56f98SSadaf Ebrahimidef backup_config(options): 103*62c56f98SSadaf Ebrahimi """Back up the library configuration file (mbedtls_config.h). 104*62c56f98SSadaf EbrahimiIf the backup file already exists, it is presumed to be the desired backup, 105*62c56f98SSadaf Ebrahimiso don't make another backup.""" 106*62c56f98SSadaf Ebrahimi if os.path.exists(options.config_backup): 107*62c56f98SSadaf Ebrahimi options.own_backup = False 108*62c56f98SSadaf Ebrahimi else: 109*62c56f98SSadaf Ebrahimi options.own_backup = True 110*62c56f98SSadaf Ebrahimi shutil.copy(options.config, options.config_backup) 111*62c56f98SSadaf Ebrahimi 112*62c56f98SSadaf Ebrahimidef restore_config(options): 113*62c56f98SSadaf Ebrahimi """Restore the library configuration file (mbedtls_config.h). 114*62c56f98SSadaf EbrahimiRemove the backup file if it was saved earlier.""" 115*62c56f98SSadaf Ebrahimi if options.own_backup: 116*62c56f98SSadaf Ebrahimi shutil.move(options.config_backup, options.config) 117*62c56f98SSadaf Ebrahimi else: 118*62c56f98SSadaf Ebrahimi shutil.copy(options.config_backup, options.config) 119*62c56f98SSadaf Ebrahimi 120*62c56f98SSadaf Ebrahimidef option_exists(conf, option): 121*62c56f98SSadaf Ebrahimi return option in conf.settings 122*62c56f98SSadaf Ebrahimi 123*62c56f98SSadaf Ebrahimidef set_config_option_value(conf, option, colors, value: Union[bool, str]): 124*62c56f98SSadaf Ebrahimi """Set/unset a configuration option, optionally specifying a value. 125*62c56f98SSadaf Ebrahimivalue can be either True/False (set/unset config option), or a string, 126*62c56f98SSadaf Ebrahimiwhich will make a symbol defined with a certain value.""" 127*62c56f98SSadaf Ebrahimi if not option_exists(conf, option): 128*62c56f98SSadaf Ebrahimi log_line('Symbol {} was not found in {}'.format(option, conf.filename), color=colors.red) 129*62c56f98SSadaf Ebrahimi return False 130*62c56f98SSadaf Ebrahimi 131*62c56f98SSadaf Ebrahimi if value is False: 132*62c56f98SSadaf Ebrahimi log_command(['config.py', 'unset', option]) 133*62c56f98SSadaf Ebrahimi conf.unset(option) 134*62c56f98SSadaf Ebrahimi elif value is True: 135*62c56f98SSadaf Ebrahimi log_command(['config.py', 'set', option]) 136*62c56f98SSadaf Ebrahimi conf.set(option) 137*62c56f98SSadaf Ebrahimi else: 138*62c56f98SSadaf Ebrahimi log_command(['config.py', 'set', option, value]) 139*62c56f98SSadaf Ebrahimi conf.set(option, value) 140*62c56f98SSadaf Ebrahimi return True 141*62c56f98SSadaf Ebrahimi 142*62c56f98SSadaf Ebrahimidef set_reference_config(conf, options, colors): 143*62c56f98SSadaf Ebrahimi """Change the library configuration file (mbedtls_config.h) to the reference state. 144*62c56f98SSadaf EbrahimiThe reference state is the one from which the tested configurations are 145*62c56f98SSadaf Ebrahimiderived.""" 146*62c56f98SSadaf Ebrahimi # Turn off options that are not relevant to the tests and slow them down. 147*62c56f98SSadaf Ebrahimi log_command(['config.py', 'full']) 148*62c56f98SSadaf Ebrahimi conf.adapt(config.full_adapter) 149*62c56f98SSadaf Ebrahimi set_config_option_value(conf, 'MBEDTLS_TEST_HOOKS', colors, False) 150*62c56f98SSadaf Ebrahimi set_config_option_value(conf, 'MBEDTLS_PSA_CRYPTO_CONFIG', colors, False) 151*62c56f98SSadaf Ebrahimi if options.unset_use_psa: 152*62c56f98SSadaf Ebrahimi set_config_option_value(conf, 'MBEDTLS_USE_PSA_CRYPTO', colors, False) 153*62c56f98SSadaf Ebrahimi 154*62c56f98SSadaf Ebrahimiclass Job: 155*62c56f98SSadaf Ebrahimi """A job builds the library in a specific configuration and runs some tests.""" 156*62c56f98SSadaf Ebrahimi def __init__(self, name, config_settings, commands): 157*62c56f98SSadaf Ebrahimi """Build a job object. 158*62c56f98SSadaf EbrahimiThe job uses the configuration described by config_settings. This is a 159*62c56f98SSadaf Ebrahimidictionary where the keys are preprocessor symbols and the values are 160*62c56f98SSadaf Ebrahimibooleans or strings. A boolean indicates whether or not to #define the 161*62c56f98SSadaf Ebrahimisymbol. With a string, the symbol is #define'd to that value. 162*62c56f98SSadaf EbrahimiAfter setting the configuration, the job runs the programs specified by 163*62c56f98SSadaf Ebrahimicommands. This is a list of lists of strings; each list of string is a 164*62c56f98SSadaf Ebrahimicommand name and its arguments and is passed to subprocess.call with 165*62c56f98SSadaf Ebrahimishell=False.""" 166*62c56f98SSadaf Ebrahimi self.name = name 167*62c56f98SSadaf Ebrahimi self.config_settings = config_settings 168*62c56f98SSadaf Ebrahimi self.commands = commands 169*62c56f98SSadaf Ebrahimi 170*62c56f98SSadaf Ebrahimi def announce(self, colors, what): 171*62c56f98SSadaf Ebrahimi '''Announce the start or completion of a job. 172*62c56f98SSadaf EbrahimiIf what is None, announce the start of the job. 173*62c56f98SSadaf EbrahimiIf what is True, announce that the job has passed. 174*62c56f98SSadaf EbrahimiIf what is False, announce that the job has failed.''' 175*62c56f98SSadaf Ebrahimi if what is True: 176*62c56f98SSadaf Ebrahimi log_line(self.name + ' PASSED', color=colors.green) 177*62c56f98SSadaf Ebrahimi elif what is False: 178*62c56f98SSadaf Ebrahimi log_line(self.name + ' FAILED', color=colors.red) 179*62c56f98SSadaf Ebrahimi else: 180*62c56f98SSadaf Ebrahimi log_line('starting ' + self.name, color=colors.cyan) 181*62c56f98SSadaf Ebrahimi 182*62c56f98SSadaf Ebrahimi def configure(self, conf, options, colors): 183*62c56f98SSadaf Ebrahimi '''Set library configuration options as required for the job.''' 184*62c56f98SSadaf Ebrahimi set_reference_config(conf, options, colors) 185*62c56f98SSadaf Ebrahimi for key, value in sorted(self.config_settings.items()): 186*62c56f98SSadaf Ebrahimi ret = set_config_option_value(conf, key, colors, value) 187*62c56f98SSadaf Ebrahimi if ret is False: 188*62c56f98SSadaf Ebrahimi return False 189*62c56f98SSadaf Ebrahimi return True 190*62c56f98SSadaf Ebrahimi 191*62c56f98SSadaf Ebrahimi def test(self, options): 192*62c56f98SSadaf Ebrahimi '''Run the job's build and test commands. 193*62c56f98SSadaf EbrahimiReturn True if all the commands succeed and False otherwise. 194*62c56f98SSadaf EbrahimiIf options.keep_going is false, stop as soon as one command fails. Otherwise 195*62c56f98SSadaf Ebrahimirun all the commands, except that if the first command fails, none of the 196*62c56f98SSadaf Ebrahimiother commands are run (typically, the first command is a build command 197*62c56f98SSadaf Ebrahimiand subsequent commands are tests that cannot run if the build failed).''' 198*62c56f98SSadaf Ebrahimi built = False 199*62c56f98SSadaf Ebrahimi success = True 200*62c56f98SSadaf Ebrahimi for command in self.commands: 201*62c56f98SSadaf Ebrahimi log_command(command) 202*62c56f98SSadaf Ebrahimi ret = subprocess.call(command) 203*62c56f98SSadaf Ebrahimi if ret != 0: 204*62c56f98SSadaf Ebrahimi if command[0] not in ['make', options.make_command]: 205*62c56f98SSadaf Ebrahimi log_line('*** [{}] Error {}'.format(' '.join(command), ret)) 206*62c56f98SSadaf Ebrahimi if not options.keep_going or not built: 207*62c56f98SSadaf Ebrahimi return False 208*62c56f98SSadaf Ebrahimi success = False 209*62c56f98SSadaf Ebrahimi built = True 210*62c56f98SSadaf Ebrahimi return success 211*62c56f98SSadaf Ebrahimi 212*62c56f98SSadaf Ebrahimi# If the configuration option A requires B, make sure that 213*62c56f98SSadaf Ebrahimi# B in REVERSE_DEPENDENCIES[A]. 214*62c56f98SSadaf Ebrahimi# All the information here should be contained in check_config.h. This 215*62c56f98SSadaf Ebrahimi# file includes a copy because it changes rarely and it would be a pain 216*62c56f98SSadaf Ebrahimi# to extract automatically. 217*62c56f98SSadaf EbrahimiREVERSE_DEPENDENCIES = { 218*62c56f98SSadaf Ebrahimi 'MBEDTLS_AES_C': ['MBEDTLS_CTR_DRBG_C', 219*62c56f98SSadaf Ebrahimi 'MBEDTLS_NIST_KW_C'], 220*62c56f98SSadaf Ebrahimi 'MBEDTLS_CHACHA20_C': ['MBEDTLS_CHACHAPOLY_C'], 221*62c56f98SSadaf Ebrahimi 'MBEDTLS_ECDSA_C': ['MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED', 222*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED'], 223*62c56f98SSadaf Ebrahimi 'MBEDTLS_ECP_C': ['MBEDTLS_ECDSA_C', 224*62c56f98SSadaf Ebrahimi 'MBEDTLS_ECDH_C', 225*62c56f98SSadaf Ebrahimi 'MBEDTLS_ECJPAKE_C', 226*62c56f98SSadaf Ebrahimi 'MBEDTLS_ECP_RESTARTABLE', 227*62c56f98SSadaf Ebrahimi 'MBEDTLS_PK_PARSE_EC_EXTENDED', 228*62c56f98SSadaf Ebrahimi 'MBEDTLS_PK_PARSE_EC_COMPRESSED', 229*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED', 230*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED', 231*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED', 232*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED', 233*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED', 234*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED', 235*62c56f98SSadaf Ebrahimi 'MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED', 236*62c56f98SSadaf Ebrahimi 'MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED'], 237*62c56f98SSadaf Ebrahimi 'MBEDTLS_ECP_DP_SECP256R1_ENABLED': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED'], 238*62c56f98SSadaf Ebrahimi 'MBEDTLS_PKCS1_V21': ['MBEDTLS_X509_RSASSA_PSS_SUPPORT'], 239*62c56f98SSadaf Ebrahimi 'MBEDTLS_PKCS1_V15': ['MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED', 240*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED', 241*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED', 242*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_RSA_ENABLED'], 243*62c56f98SSadaf Ebrahimi 'MBEDTLS_RSA_C': ['MBEDTLS_X509_RSASSA_PSS_SUPPORT', 244*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED', 245*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED', 246*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED', 247*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_RSA_ENABLED', 248*62c56f98SSadaf Ebrahimi 'MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED'], 249*62c56f98SSadaf Ebrahimi 'MBEDTLS_SHA256_C': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED', 250*62c56f98SSadaf Ebrahimi 'MBEDTLS_ENTROPY_FORCE_SHA256', 251*62c56f98SSadaf Ebrahimi 'MBEDTLS_SHA256_USE_A64_CRYPTO_IF_PRESENT', 252*62c56f98SSadaf Ebrahimi 'MBEDTLS_SHA256_USE_A64_CRYPTO_ONLY', 253*62c56f98SSadaf Ebrahimi 'MBEDTLS_LMS_C', 254*62c56f98SSadaf Ebrahimi 'MBEDTLS_LMS_PRIVATE'], 255*62c56f98SSadaf Ebrahimi 'MBEDTLS_SHA512_C': ['MBEDTLS_SHA512_USE_A64_CRYPTO_IF_PRESENT', 256*62c56f98SSadaf Ebrahimi 'MBEDTLS_SHA512_USE_A64_CRYPTO_ONLY'], 257*62c56f98SSadaf Ebrahimi 'MBEDTLS_SHA224_C': ['MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED', 258*62c56f98SSadaf Ebrahimi 'MBEDTLS_ENTROPY_FORCE_SHA256', 259*62c56f98SSadaf Ebrahimi 'MBEDTLS_SHA256_USE_A64_CRYPTO_IF_PRESENT', 260*62c56f98SSadaf Ebrahimi 'MBEDTLS_SHA256_USE_A64_CRYPTO_ONLY'], 261*62c56f98SSadaf Ebrahimi 'MBEDTLS_X509_RSASSA_PSS_SUPPORT': [] 262*62c56f98SSadaf Ebrahimi} 263*62c56f98SSadaf Ebrahimi 264*62c56f98SSadaf Ebrahimi# If an option is tested in an exclusive test, alter the following defines. 265*62c56f98SSadaf Ebrahimi# These are not necessarily dependencies, but just minimal required changes 266*62c56f98SSadaf Ebrahimi# if a given define is the only one enabled from an exclusive group. 267*62c56f98SSadaf EbrahimiEXCLUSIVE_GROUPS = { 268*62c56f98SSadaf Ebrahimi 'MBEDTLS_SHA512_C': ['-MBEDTLS_SSL_COOKIE_C', 269*62c56f98SSadaf Ebrahimi '-MBEDTLS_SSL_TLS_C'], 270*62c56f98SSadaf Ebrahimi 'MBEDTLS_ECP_DP_CURVE448_ENABLED': ['-MBEDTLS_ECDSA_C', 271*62c56f98SSadaf Ebrahimi '-MBEDTLS_ECDSA_DETERMINISTIC', 272*62c56f98SSadaf Ebrahimi '-MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED', 273*62c56f98SSadaf Ebrahimi '-MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED', 274*62c56f98SSadaf Ebrahimi '-MBEDTLS_ECJPAKE_C', 275*62c56f98SSadaf Ebrahimi '-MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED'], 276*62c56f98SSadaf Ebrahimi 'MBEDTLS_ECP_DP_CURVE25519_ENABLED': ['-MBEDTLS_ECDSA_C', 277*62c56f98SSadaf Ebrahimi '-MBEDTLS_ECDSA_DETERMINISTIC', 278*62c56f98SSadaf Ebrahimi '-MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED', 279*62c56f98SSadaf Ebrahimi '-MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED', 280*62c56f98SSadaf Ebrahimi '-MBEDTLS_ECJPAKE_C', 281*62c56f98SSadaf Ebrahimi '-MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED'], 282*62c56f98SSadaf Ebrahimi 'MBEDTLS_ARIA_C': ['-MBEDTLS_CMAC_C'], 283*62c56f98SSadaf Ebrahimi 'MBEDTLS_CAMELLIA_C': ['-MBEDTLS_CMAC_C'], 284*62c56f98SSadaf Ebrahimi 'MBEDTLS_CHACHA20_C': ['-MBEDTLS_CMAC_C', '-MBEDTLS_CCM_C', '-MBEDTLS_GCM_C'], 285*62c56f98SSadaf Ebrahimi 'MBEDTLS_DES_C': ['-MBEDTLS_CCM_C', 286*62c56f98SSadaf Ebrahimi '-MBEDTLS_GCM_C', 287*62c56f98SSadaf Ebrahimi '-MBEDTLS_SSL_TICKET_C', 288*62c56f98SSadaf Ebrahimi '-MBEDTLS_SSL_CONTEXT_SERIALIZATION'], 289*62c56f98SSadaf Ebrahimi} 290*62c56f98SSadaf Ebrahimidef handle_exclusive_groups(config_settings, symbol): 291*62c56f98SSadaf Ebrahimi """For every symbol tested in an exclusive group check if there are other 292*62c56f98SSadaf Ebrahimidefines to be altered. """ 293*62c56f98SSadaf Ebrahimi for dep in EXCLUSIVE_GROUPS.get(symbol, []): 294*62c56f98SSadaf Ebrahimi unset = dep.startswith('-') 295*62c56f98SSadaf Ebrahimi dep = dep[1:] 296*62c56f98SSadaf Ebrahimi config_settings[dep] = not unset 297*62c56f98SSadaf Ebrahimi 298*62c56f98SSadaf Ebrahimidef turn_off_dependencies(config_settings): 299*62c56f98SSadaf Ebrahimi """For every option turned off config_settings, also turn off what depends on it. 300*62c56f98SSadaf EbrahimiAn option O is turned off if config_settings[O] is False.""" 301*62c56f98SSadaf Ebrahimi for key, value in sorted(config_settings.items()): 302*62c56f98SSadaf Ebrahimi if value is not False: 303*62c56f98SSadaf Ebrahimi continue 304*62c56f98SSadaf Ebrahimi for dep in REVERSE_DEPENDENCIES.get(key, []): 305*62c56f98SSadaf Ebrahimi config_settings[dep] = False 306*62c56f98SSadaf Ebrahimi 307*62c56f98SSadaf Ebrahimiclass BaseDomain: # pylint: disable=too-few-public-methods, unused-argument 308*62c56f98SSadaf Ebrahimi """A base class for all domains.""" 309*62c56f98SSadaf Ebrahimi def __init__(self, symbols, commands, exclude): 310*62c56f98SSadaf Ebrahimi """Initialize the jobs container""" 311*62c56f98SSadaf Ebrahimi self.jobs = [] 312*62c56f98SSadaf Ebrahimi 313*62c56f98SSadaf Ebrahimiclass ExclusiveDomain(BaseDomain): # pylint: disable=too-few-public-methods 314*62c56f98SSadaf Ebrahimi """A domain consisting of a set of conceptually-equivalent settings. 315*62c56f98SSadaf EbrahimiEstablish a list of configuration symbols. For each symbol, run a test job 316*62c56f98SSadaf Ebrahimiwith this symbol set and the others unset.""" 317*62c56f98SSadaf Ebrahimi def __init__(self, symbols, commands, exclude=None): 318*62c56f98SSadaf Ebrahimi """Build a domain for the specified list of configuration symbols. 319*62c56f98SSadaf EbrahimiThe domain contains a set of jobs that enable one of the elements 320*62c56f98SSadaf Ebrahimiof symbols and disable the others. 321*62c56f98SSadaf EbrahimiEach job runs the specified commands. 322*62c56f98SSadaf EbrahimiIf exclude is a regular expression, skip generated jobs whose description 323*62c56f98SSadaf Ebrahimiwould match this regular expression.""" 324*62c56f98SSadaf Ebrahimi super().__init__(symbols, commands, exclude) 325*62c56f98SSadaf Ebrahimi base_config_settings = {} 326*62c56f98SSadaf Ebrahimi for symbol in symbols: 327*62c56f98SSadaf Ebrahimi base_config_settings[symbol] = False 328*62c56f98SSadaf Ebrahimi for symbol in symbols: 329*62c56f98SSadaf Ebrahimi description = symbol 330*62c56f98SSadaf Ebrahimi if exclude and re.match(exclude, description): 331*62c56f98SSadaf Ebrahimi continue 332*62c56f98SSadaf Ebrahimi config_settings = base_config_settings.copy() 333*62c56f98SSadaf Ebrahimi config_settings[symbol] = True 334*62c56f98SSadaf Ebrahimi handle_exclusive_groups(config_settings, symbol) 335*62c56f98SSadaf Ebrahimi turn_off_dependencies(config_settings) 336*62c56f98SSadaf Ebrahimi job = Job(description, config_settings, commands) 337*62c56f98SSadaf Ebrahimi self.jobs.append(job) 338*62c56f98SSadaf Ebrahimi 339*62c56f98SSadaf Ebrahimiclass ComplementaryDomain(BaseDomain): # pylint: disable=too-few-public-methods 340*62c56f98SSadaf Ebrahimi """A domain consisting of a set of loosely-related settings. 341*62c56f98SSadaf EbrahimiEstablish a list of configuration symbols. For each symbol, run a test job 342*62c56f98SSadaf Ebrahimiwith this symbol unset. 343*62c56f98SSadaf EbrahimiIf exclude is a regular expression, skip generated jobs whose description 344*62c56f98SSadaf Ebrahimiwould match this regular expression.""" 345*62c56f98SSadaf Ebrahimi def __init__(self, symbols, commands, exclude=None): 346*62c56f98SSadaf Ebrahimi """Build a domain for the specified list of configuration symbols. 347*62c56f98SSadaf EbrahimiEach job in the domain disables one of the specified symbols. 348*62c56f98SSadaf EbrahimiEach job runs the specified commands.""" 349*62c56f98SSadaf Ebrahimi super().__init__(symbols, commands, exclude) 350*62c56f98SSadaf Ebrahimi for symbol in symbols: 351*62c56f98SSadaf Ebrahimi description = '!' + symbol 352*62c56f98SSadaf Ebrahimi if exclude and re.match(exclude, description): 353*62c56f98SSadaf Ebrahimi continue 354*62c56f98SSadaf Ebrahimi config_settings = {symbol: False} 355*62c56f98SSadaf Ebrahimi turn_off_dependencies(config_settings) 356*62c56f98SSadaf Ebrahimi job = Job(description, config_settings, commands) 357*62c56f98SSadaf Ebrahimi self.jobs.append(job) 358*62c56f98SSadaf Ebrahimi 359*62c56f98SSadaf Ebrahimiclass DualDomain(ExclusiveDomain, ComplementaryDomain): # pylint: disable=too-few-public-methods 360*62c56f98SSadaf Ebrahimi """A domain that contains both the ExclusiveDomain and BaseDomain tests. 361*62c56f98SSadaf EbrahimiBoth parent class __init__ calls are performed in any order and 362*62c56f98SSadaf Ebrahimieach call adds respective jobs. The job array initialization is done once in 363*62c56f98SSadaf EbrahimiBaseDomain, before the parent __init__ calls.""" 364*62c56f98SSadaf Ebrahimi 365*62c56f98SSadaf Ebrahimiclass CipherInfo: # pylint: disable=too-few-public-methods 366*62c56f98SSadaf Ebrahimi """Collect data about cipher.h.""" 367*62c56f98SSadaf Ebrahimi def __init__(self): 368*62c56f98SSadaf Ebrahimi self.base_symbols = set() 369*62c56f98SSadaf Ebrahimi with open('include/mbedtls/cipher.h', encoding="utf-8") as fh: 370*62c56f98SSadaf Ebrahimi for line in fh: 371*62c56f98SSadaf Ebrahimi m = re.match(r' *MBEDTLS_CIPHER_ID_(\w+),', line) 372*62c56f98SSadaf Ebrahimi if m and m.group(1) not in ['NONE', 'NULL', '3DES']: 373*62c56f98SSadaf Ebrahimi self.base_symbols.add('MBEDTLS_' + m.group(1) + '_C') 374*62c56f98SSadaf Ebrahimi 375*62c56f98SSadaf Ebrahimiclass DomainData: 376*62c56f98SSadaf Ebrahimi """A container for domains and jobs, used to structurize testing.""" 377*62c56f98SSadaf Ebrahimi def config_symbols_matching(self, regexp): 378*62c56f98SSadaf Ebrahimi """List the mbedtls_config.h settings matching regexp.""" 379*62c56f98SSadaf Ebrahimi return [symbol for symbol in self.all_config_symbols 380*62c56f98SSadaf Ebrahimi if re.match(regexp, symbol)] 381*62c56f98SSadaf Ebrahimi 382*62c56f98SSadaf Ebrahimi def __init__(self, options, conf): 383*62c56f98SSadaf Ebrahimi """Gather data about the library and establish a list of domains to test.""" 384*62c56f98SSadaf Ebrahimi build_command = [options.make_command, 'CFLAGS=-Werror'] 385*62c56f98SSadaf Ebrahimi build_and_test = [build_command, [options.make_command, 'test']] 386*62c56f98SSadaf Ebrahimi self.all_config_symbols = set(conf.settings.keys()) 387*62c56f98SSadaf Ebrahimi # Find hash modules by name. 388*62c56f98SSadaf Ebrahimi hash_symbols = self.config_symbols_matching(r'MBEDTLS_(MD|RIPEMD|SHA)[0-9]+_C\Z') 389*62c56f98SSadaf Ebrahimi # Find elliptic curve enabling macros by name. 390*62c56f98SSadaf Ebrahimi curve_symbols = self.config_symbols_matching(r'MBEDTLS_ECP_DP_\w+_ENABLED\Z') 391*62c56f98SSadaf Ebrahimi # Find key exchange enabling macros by name. 392*62c56f98SSadaf Ebrahimi key_exchange_symbols = self.config_symbols_matching(r'MBEDTLS_KEY_EXCHANGE_\w+_ENABLED\Z') 393*62c56f98SSadaf Ebrahimi # Find cipher IDs (block permutations and stream ciphers --- chaining 394*62c56f98SSadaf Ebrahimi # and padding modes are exercised separately) information by parsing 395*62c56f98SSadaf Ebrahimi # cipher.h, as the information is not readily available in mbedtls_config.h. 396*62c56f98SSadaf Ebrahimi cipher_info = CipherInfo() 397*62c56f98SSadaf Ebrahimi # Find block cipher chaining and padding mode enabling macros by name. 398*62c56f98SSadaf Ebrahimi cipher_chaining_symbols = self.config_symbols_matching(r'MBEDTLS_CIPHER_MODE_\w+\Z') 399*62c56f98SSadaf Ebrahimi cipher_padding_symbols = self.config_symbols_matching(r'MBEDTLS_CIPHER_PADDING_\w+\Z') 400*62c56f98SSadaf Ebrahimi self.domains = { 401*62c56f98SSadaf Ebrahimi # Cipher IDs, chaining modes and padding modes. Run the test suites. 402*62c56f98SSadaf Ebrahimi 'cipher_id': ExclusiveDomain(cipher_info.base_symbols, 403*62c56f98SSadaf Ebrahimi build_and_test), 404*62c56f98SSadaf Ebrahimi 'cipher_chaining': ExclusiveDomain(cipher_chaining_symbols, 405*62c56f98SSadaf Ebrahimi build_and_test), 406*62c56f98SSadaf Ebrahimi 'cipher_padding': ExclusiveDomain(cipher_padding_symbols, 407*62c56f98SSadaf Ebrahimi build_and_test), 408*62c56f98SSadaf Ebrahimi # Elliptic curves. Run the test suites. 409*62c56f98SSadaf Ebrahimi 'curves': ExclusiveDomain(curve_symbols, build_and_test), 410*62c56f98SSadaf Ebrahimi # Hash algorithms. Excluding exclusive domains of MD, RIPEMD, SHA1, 411*62c56f98SSadaf Ebrahimi # SHA224 and SHA384 because MBEDTLS_ENTROPY_C is extensively used 412*62c56f98SSadaf Ebrahimi # across various modules, but it depends on either SHA256 or SHA512. 413*62c56f98SSadaf Ebrahimi # As a consequence an "exclusive" test of anything other than SHA256 414*62c56f98SSadaf Ebrahimi # or SHA512 with MBEDTLS_ENTROPY_C enabled is not possible. 415*62c56f98SSadaf Ebrahimi 'hashes': DualDomain(hash_symbols, build_and_test, 416*62c56f98SSadaf Ebrahimi exclude=r'MBEDTLS_(MD|RIPEMD|SHA1_)' \ 417*62c56f98SSadaf Ebrahimi '|MBEDTLS_SHA224_' \ 418*62c56f98SSadaf Ebrahimi '|MBEDTLS_SHA384_' \ 419*62c56f98SSadaf Ebrahimi '|MBEDTLS_SHA3_'), 420*62c56f98SSadaf Ebrahimi # Key exchange types. 421*62c56f98SSadaf Ebrahimi 'kex': ExclusiveDomain(key_exchange_symbols, build_and_test), 422*62c56f98SSadaf Ebrahimi 'pkalgs': ComplementaryDomain(['MBEDTLS_ECDSA_C', 423*62c56f98SSadaf Ebrahimi 'MBEDTLS_ECP_C', 424*62c56f98SSadaf Ebrahimi 'MBEDTLS_PKCS1_V21', 425*62c56f98SSadaf Ebrahimi 'MBEDTLS_PKCS1_V15', 426*62c56f98SSadaf Ebrahimi 'MBEDTLS_RSA_C', 427*62c56f98SSadaf Ebrahimi 'MBEDTLS_X509_RSASSA_PSS_SUPPORT'], 428*62c56f98SSadaf Ebrahimi build_and_test), 429*62c56f98SSadaf Ebrahimi } 430*62c56f98SSadaf Ebrahimi self.jobs = {} 431*62c56f98SSadaf Ebrahimi for domain in self.domains.values(): 432*62c56f98SSadaf Ebrahimi for job in domain.jobs: 433*62c56f98SSadaf Ebrahimi self.jobs[job.name] = job 434*62c56f98SSadaf Ebrahimi 435*62c56f98SSadaf Ebrahimi def get_jobs(self, name): 436*62c56f98SSadaf Ebrahimi """Return the list of jobs identified by the given name. 437*62c56f98SSadaf EbrahimiA name can either be the name of a domain or the name of one specific job.""" 438*62c56f98SSadaf Ebrahimi if name in self.domains: 439*62c56f98SSadaf Ebrahimi return sorted(self.domains[name].jobs, key=lambda job: job.name) 440*62c56f98SSadaf Ebrahimi else: 441*62c56f98SSadaf Ebrahimi return [self.jobs[name]] 442*62c56f98SSadaf Ebrahimi 443*62c56f98SSadaf Ebrahimidef run(options, job, conf, colors=NO_COLORS): 444*62c56f98SSadaf Ebrahimi """Run the specified job (a Job instance).""" 445*62c56f98SSadaf Ebrahimi subprocess.check_call([options.make_command, 'clean']) 446*62c56f98SSadaf Ebrahimi job.announce(colors, None) 447*62c56f98SSadaf Ebrahimi if not job.configure(conf, options, colors): 448*62c56f98SSadaf Ebrahimi job.announce(colors, False) 449*62c56f98SSadaf Ebrahimi return False 450*62c56f98SSadaf Ebrahimi conf.write() 451*62c56f98SSadaf Ebrahimi success = job.test(options) 452*62c56f98SSadaf Ebrahimi job.announce(colors, success) 453*62c56f98SSadaf Ebrahimi return success 454*62c56f98SSadaf Ebrahimi 455*62c56f98SSadaf Ebrahimidef run_tests(options, domain_data, conf): 456*62c56f98SSadaf Ebrahimi """Run the desired jobs. 457*62c56f98SSadaf Ebrahimidomain_data should be a DomainData instance that describes the available 458*62c56f98SSadaf Ebrahimidomains and jobs. 459*62c56f98SSadaf EbrahimiRun the jobs listed in options.tasks.""" 460*62c56f98SSadaf Ebrahimi if not hasattr(options, 'config_backup'): 461*62c56f98SSadaf Ebrahimi options.config_backup = options.config + '.bak' 462*62c56f98SSadaf Ebrahimi colors = Colors(options) 463*62c56f98SSadaf Ebrahimi jobs = [] 464*62c56f98SSadaf Ebrahimi failures = [] 465*62c56f98SSadaf Ebrahimi successes = [] 466*62c56f98SSadaf Ebrahimi for name in options.tasks: 467*62c56f98SSadaf Ebrahimi jobs += domain_data.get_jobs(name) 468*62c56f98SSadaf Ebrahimi backup_config(options) 469*62c56f98SSadaf Ebrahimi try: 470*62c56f98SSadaf Ebrahimi for job in jobs: 471*62c56f98SSadaf Ebrahimi success = run(options, job, conf, colors=colors) 472*62c56f98SSadaf Ebrahimi if not success: 473*62c56f98SSadaf Ebrahimi if options.keep_going: 474*62c56f98SSadaf Ebrahimi failures.append(job.name) 475*62c56f98SSadaf Ebrahimi else: 476*62c56f98SSadaf Ebrahimi return False 477*62c56f98SSadaf Ebrahimi else: 478*62c56f98SSadaf Ebrahimi successes.append(job.name) 479*62c56f98SSadaf Ebrahimi restore_config(options) 480*62c56f98SSadaf Ebrahimi except: 481*62c56f98SSadaf Ebrahimi # Restore the configuration, except in stop-on-error mode if there 482*62c56f98SSadaf Ebrahimi # was an error, where we leave the failing configuration up for 483*62c56f98SSadaf Ebrahimi # developer convenience. 484*62c56f98SSadaf Ebrahimi if options.keep_going: 485*62c56f98SSadaf Ebrahimi restore_config(options) 486*62c56f98SSadaf Ebrahimi raise 487*62c56f98SSadaf Ebrahimi if successes: 488*62c56f98SSadaf Ebrahimi log_line('{} passed'.format(' '.join(successes)), color=colors.bold_green) 489*62c56f98SSadaf Ebrahimi if failures: 490*62c56f98SSadaf Ebrahimi log_line('{} FAILED'.format(' '.join(failures)), color=colors.bold_red) 491*62c56f98SSadaf Ebrahimi return False 492*62c56f98SSadaf Ebrahimi else: 493*62c56f98SSadaf Ebrahimi return True 494*62c56f98SSadaf Ebrahimi 495*62c56f98SSadaf Ebrahimidef main(): 496*62c56f98SSadaf Ebrahimi try: 497*62c56f98SSadaf Ebrahimi parser = argparse.ArgumentParser( 498*62c56f98SSadaf Ebrahimi formatter_class=argparse.RawDescriptionHelpFormatter, 499*62c56f98SSadaf Ebrahimi description= 500*62c56f98SSadaf Ebrahimi "Test Mbed TLS with a subset of algorithms.\n\n" 501*62c56f98SSadaf Ebrahimi "Example usage:\n" 502*62c56f98SSadaf Ebrahimi r"./tests/scripts/depends.py \!MBEDTLS_SHA1_C MBEDTLS_SHA256_C""\n" 503*62c56f98SSadaf Ebrahimi "./tests/scripts/depends.py MBEDTLS_AES_C hashes\n" 504*62c56f98SSadaf Ebrahimi "./tests/scripts/depends.py cipher_id cipher_chaining\n") 505*62c56f98SSadaf Ebrahimi parser.add_argument('--color', metavar='WHEN', 506*62c56f98SSadaf Ebrahimi help='Colorize the output (always/auto/never)', 507*62c56f98SSadaf Ebrahimi choices=['always', 'auto', 'never'], default='auto') 508*62c56f98SSadaf Ebrahimi parser.add_argument('-c', '--config', metavar='FILE', 509*62c56f98SSadaf Ebrahimi help='Configuration file to modify', 510*62c56f98SSadaf Ebrahimi default='include/mbedtls/mbedtls_config.h') 511*62c56f98SSadaf Ebrahimi parser.add_argument('-C', '--directory', metavar='DIR', 512*62c56f98SSadaf Ebrahimi help='Change to this directory before anything else', 513*62c56f98SSadaf Ebrahimi default='.') 514*62c56f98SSadaf Ebrahimi parser.add_argument('-k', '--keep-going', 515*62c56f98SSadaf Ebrahimi help='Try all configurations even if some fail (default)', 516*62c56f98SSadaf Ebrahimi action='store_true', dest='keep_going', default=True) 517*62c56f98SSadaf Ebrahimi parser.add_argument('-e', '--no-keep-going', 518*62c56f98SSadaf Ebrahimi help='Stop as soon as a configuration fails', 519*62c56f98SSadaf Ebrahimi action='store_false', dest='keep_going') 520*62c56f98SSadaf Ebrahimi parser.add_argument('--list-jobs', 521*62c56f98SSadaf Ebrahimi help='List supported jobs and exit', 522*62c56f98SSadaf Ebrahimi action='append_const', dest='list', const='jobs') 523*62c56f98SSadaf Ebrahimi parser.add_argument('--list-domains', 524*62c56f98SSadaf Ebrahimi help='List supported domains and exit', 525*62c56f98SSadaf Ebrahimi action='append_const', dest='list', const='domains') 526*62c56f98SSadaf Ebrahimi parser.add_argument('--make-command', metavar='CMD', 527*62c56f98SSadaf Ebrahimi help='Command to run instead of make (e.g. gmake)', 528*62c56f98SSadaf Ebrahimi action='store', default='make') 529*62c56f98SSadaf Ebrahimi parser.add_argument('--unset-use-psa', 530*62c56f98SSadaf Ebrahimi help='Unset MBEDTLS_USE_PSA_CRYPTO before any test', 531*62c56f98SSadaf Ebrahimi action='store_true', dest='unset_use_psa') 532*62c56f98SSadaf Ebrahimi parser.add_argument('tasks', metavar='TASKS', nargs='*', 533*62c56f98SSadaf Ebrahimi help='The domain(s) or job(s) to test (default: all).', 534*62c56f98SSadaf Ebrahimi default=True) 535*62c56f98SSadaf Ebrahimi options = parser.parse_args() 536*62c56f98SSadaf Ebrahimi os.chdir(options.directory) 537*62c56f98SSadaf Ebrahimi conf = config.ConfigFile(options.config) 538*62c56f98SSadaf Ebrahimi domain_data = DomainData(options, conf) 539*62c56f98SSadaf Ebrahimi 540*62c56f98SSadaf Ebrahimi if options.tasks is True: 541*62c56f98SSadaf Ebrahimi options.tasks = sorted(domain_data.domains.keys()) 542*62c56f98SSadaf Ebrahimi if options.list: 543*62c56f98SSadaf Ebrahimi for arg in options.list: 544*62c56f98SSadaf Ebrahimi for domain_name in sorted(getattr(domain_data, arg).keys()): 545*62c56f98SSadaf Ebrahimi print(domain_name) 546*62c56f98SSadaf Ebrahimi sys.exit(0) 547*62c56f98SSadaf Ebrahimi else: 548*62c56f98SSadaf Ebrahimi sys.exit(0 if run_tests(options, domain_data, conf) else 1) 549*62c56f98SSadaf Ebrahimi except Exception: # pylint: disable=broad-except 550*62c56f98SSadaf Ebrahimi traceback.print_exc() 551*62c56f98SSadaf Ebrahimi sys.exit(3) 552*62c56f98SSadaf Ebrahimi 553*62c56f98SSadaf Ebrahimiif __name__ == '__main__': 554*62c56f98SSadaf Ebrahimi main() 555