blob: 2267311e44c637a8d6cd460ba01b19ff6366bc5b [file] [log] [blame]
Gilles Peskinebdffaea2021-01-12 00:37:38 +01001#!/usr/bin/env python3
2
3"""Edit test cases to use PSA dependencies instead of classic dependencies.
4"""
5
6# Copyright The Mbed TLS Contributors
Dave Rodgman16799db2023-11-02 19:47:20 +00007# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Gilles Peskinebdffaea2021-01-12 00:37:38 +01008
9import os
Gilles Peskine82ebaa42021-01-12 00:45:14 +010010import re
Gilles Peskinebdffaea2021-01-12 00:37:38 +010011import sys
12
Gilles Peskine9bbba5e2021-01-12 00:55:55 +010013CLASSIC_DEPENDENCIES = frozenset([
Bence Szépkútibb0cfeb2021-05-28 09:42:25 +020014 # This list is manually filtered from mbedtls_config.h.
Gilles Peskine81dec002021-01-12 00:59:09 +010015
16 # Mbed TLS feature support.
17 # Only features that affect what can be done are listed here.
18 # Options that control optimizations or alternative implementations
19 # are omitted.
Ronald Cron9f97c6e2021-03-18 16:05:03 +010020 'MBEDTLS_CIPHER_MODE_CBC',
21 'MBEDTLS_CIPHER_MODE_CFB',
22 'MBEDTLS_CIPHER_MODE_CTR',
23 'MBEDTLS_CIPHER_MODE_OFB',
24 'MBEDTLS_CIPHER_MODE_XTS',
25 'MBEDTLS_CIPHER_NULL_CIPHER',
26 'MBEDTLS_CIPHER_PADDING_PKCS7',
27 'MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS',
28 'MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN',
29 'MBEDTLS_CIPHER_PADDING_ZEROS',
Gilles Peskine81dec002021-01-12 00:59:09 +010030 #curve#'MBEDTLS_ECP_DP_SECP192R1_ENABLED',
31 #curve#'MBEDTLS_ECP_DP_SECP224R1_ENABLED',
32 #curve#'MBEDTLS_ECP_DP_SECP256R1_ENABLED',
33 #curve#'MBEDTLS_ECP_DP_SECP384R1_ENABLED',
34 #curve#'MBEDTLS_ECP_DP_SECP521R1_ENABLED',
35 #curve#'MBEDTLS_ECP_DP_SECP192K1_ENABLED',
36 #curve#'MBEDTLS_ECP_DP_SECP224K1_ENABLED',
37 #curve#'MBEDTLS_ECP_DP_SECP256K1_ENABLED',
38 #curve#'MBEDTLS_ECP_DP_BP256R1_ENABLED',
39 #curve#'MBEDTLS_ECP_DP_BP384R1_ENABLED',
40 #curve#'MBEDTLS_ECP_DP_BP512R1_ENABLED',
41 #curve#'MBEDTLS_ECP_DP_CURVE25519_ENABLED',
42 #curve#'MBEDTLS_ECP_DP_CURVE448_ENABLED',
43 'MBEDTLS_ECDSA_DETERMINISTIC',
44 #'MBEDTLS_GENPRIME', #needed for RSA key generation
45 'MBEDTLS_PKCS1_V15',
46 'MBEDTLS_PKCS1_V21',
Gilles Peskine81dec002021-01-12 00:59:09 +010047
48 # Mbed TLS modules.
49 # Only modules that provide cryptographic mechanisms are listed here.
50 # Platform, data formatting, X.509 or TLS modules are omitted.
Ronald Cron9f97c6e2021-03-18 16:05:03 +010051 'MBEDTLS_AES_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010052 'MBEDTLS_BIGNUM_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010053 'MBEDTLS_CAMELLIA_C',
54 'MBEDTLS_ARIA_C',
55 'MBEDTLS_CCM_C',
56 'MBEDTLS_CHACHA20_C',
57 'MBEDTLS_CHACHAPOLY_C',
58 'MBEDTLS_CMAC_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010059 'MBEDTLS_CTR_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010060 'MBEDTLS_DES_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010061 'MBEDTLS_ECDH_C',
62 'MBEDTLS_ECDSA_C',
63 'MBEDTLS_ECJPAKE_C',
64 'MBEDTLS_ECP_C',
65 'MBEDTLS_ENTROPY_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010066 'MBEDTLS_GCM_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010067 'MBEDTLS_HKDF_C',
68 'MBEDTLS_HMAC_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010069 'MBEDTLS_NIST_KW_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010070 'MBEDTLS_MD5_C',
71 'MBEDTLS_PKCS5_C',
72 'MBEDTLS_PKCS12_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010073 'MBEDTLS_POLY1305_C',
74 'MBEDTLS_RIPEMD160_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010075 'MBEDTLS_RSA_C',
76 'MBEDTLS_SHA1_C',
77 'MBEDTLS_SHA256_C',
78 'MBEDTLS_SHA512_C',
Gilles Peskine9bbba5e2021-01-12 00:55:55 +010079])
80
81def is_classic_dependency(dep):
82 """Whether dep is a classic dependency that PSA test cases should not use."""
83 if dep.startswith('!'):
84 dep = dep[1:]
85 return dep in CLASSIC_DEPENDENCIES
86
Gilles Peskine2d2e9242021-01-12 00:52:31 +010087def is_systematic_dependency(dep):
88 """Whether dep is a PSA dependency which is determined systematically."""
Ronald Cron6ac020d2021-03-23 17:40:47 +010089 if dep.startswith('PSA_WANT_ECC_'):
90 return False
Gilles Peskine2d2e9242021-01-12 00:52:31 +010091 return dep.startswith('PSA_WANT_')
92
Gilles Peskinefa379612021-01-12 21:14:46 +010093WITHOUT_SYSTEMATIC_DEPENDENCIES = frozenset([
Bence Szépkútia63b20d2020-12-16 11:36:46 +010094 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # only a modifier
Gilles Peskinef032fa92021-01-12 01:01:26 +010095 'PSA_ALG_ANY_HASH', # only meaningful in policies
96 'PSA_ALG_KEY_AGREEMENT', # only a way to combine algorithms
97 'PSA_ALG_TRUNCATED_MAC', # only a modifier
Gilles Peskine60b29fe2021-02-16 14:06:50 +010098 'PSA_KEY_TYPE_NONE', # not a real key type
99 'PSA_KEY_TYPE_DERIVE', # always supported, don't list it to reduce noise
100 'PSA_KEY_TYPE_RAW_DATA', # always supported, don't list it to reduce noise
Steven Cooreman2c2efa42021-02-23 09:36:42 +0100101 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', #only a modifier
102 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', #only a modifier
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100103])
104
Gilles Peskinef032fa92021-01-12 01:01:26 +0100105SPECIAL_SYSTEMATIC_DEPENDENCIES = {
106 'PSA_ALG_ECDSA_ANY': frozenset(['PSA_WANT_ALG_ECDSA']),
107 'PSA_ALG_RSA_PKCS1V15_SIGN_RAW': frozenset(['PSA_WANT_ALG_RSA_PKCS1V15_SIGN']),
108}
109
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100110def dependencies_of_symbol(symbol):
111 """Return the dependencies for a symbol that designates a cryptographic mechanism."""
Gilles Peskinefa379612021-01-12 21:14:46 +0100112 if symbol in WITHOUT_SYSTEMATIC_DEPENDENCIES:
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100113 return frozenset()
Gilles Peskinef032fa92021-01-12 01:01:26 +0100114 if symbol in SPECIAL_SYSTEMATIC_DEPENDENCIES:
115 return SPECIAL_SYSTEMATIC_DEPENDENCIES[symbol]
Gilles Peskine20987b92021-01-12 01:11:32 +0100116 if symbol.startswith('PSA_ALG_CATEGORY_') or \
117 symbol.startswith('PSA_KEY_TYPE_CATEGORY_'):
118 # Categories are used in test data when an unsupported but plausible
119 # mechanism number needed. They have no associated dependency.
120 return frozenset()
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100121 return {symbol.replace('_', '_WANT_', 1)}
122
123def systematic_dependencies(file_name, function_name, arguments):
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100124 """List the systematically determined dependency for a test case."""
125 deps = set()
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100126
127 # Run key policy negative tests even if the algorithm to attempt performing
Ronald Cron9838dc22021-03-24 09:18:23 +0100128 # is not supported but in the case where the test is to check an
129 # incompatibility between a requested algorithm for a cryptographic
130 # operation and a key policy. In the latter, we want to filter out the
131 # cases # where PSA_ERROR_NOT_SUPPORTED is returned instead of
132 # PSA_ERROR_NOT_PERMITTED.
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100133 if function_name.endswith('_key_policy') and \
Ronald Cron9838dc22021-03-24 09:18:23 +0100134 arguments[-1].startswith('PSA_ERROR_') and \
135 arguments[-1] != ('PSA_ERROR_NOT_PERMITTED'):
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100136 arguments[-2] = ''
Gilles Peskineb51d72f2021-01-12 21:15:52 +0100137 if function_name == 'copy_fail' and \
138 arguments[-1].startswith('PSA_ERROR_'):
139 arguments[-2] = ''
140 arguments[-3] = ''
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100141
Gilles Peskine80a97082021-01-12 21:18:36 +0100142 # Storage format tests that only look at how the file is structured and
143 # don't care about the format of the key material don't depend on any
144 # cryptographic mechanisms.
145 if os.path.basename(file_name) == 'test_suite_psa_crypto_persistent_key.data' and \
146 function_name in {'format_storage_data_check',
147 'parse_storage_data_check'}:
148 return []
149
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100150 for arg in arguments:
151 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
152 deps.update(dependencies_of_symbol(symbol))
153 return sorted(deps)
154
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100155def updated_dependencies(file_name, function_name, arguments, dependencies):
156 """Rework the list of dependencies into PSA_WANT_xxx.
157
158 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
159 MBEDTLS_PKCS1_V15, etc.
160
161 Add systematic PSA_WANT_xxx dependencies based on the called function and
162 its arguments, replacing existing PSA_WANT_xxx dependencies.
163 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100164 automatic = systematic_dependencies(file_name, function_name, arguments)
165 manual = [dep for dep in dependencies
Gilles Peskine9bbba5e2021-01-12 00:55:55 +0100166 if not (is_systematic_dependency(dep) or
167 is_classic_dependency(dep))]
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100168 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100169
Gilles Peskine45e9e732021-01-12 00:47:03 +0100170def keep_manual_dependencies(file_name, function_name, arguments):
171 #pylint: disable=unused-argument
172 """Declare test functions with unusual dependencies here."""
Gilles Peskinea87e1782021-01-12 01:13:39 +0100173 # If there are no arguments, we can't do any useful work. Assume that if
174 # there are dependencies, they are warranted.
175 if not arguments:
176 return True
Gilles Peskine20987b92021-01-12 01:11:32 +0100177 # When PSA_ERROR_NOT_SUPPORTED is expected, usually, at least one of the
178 # constants mentioned in the test should not be supported. It isn't
179 # possible to determine which one in a systematic way. So let the programmer
180 # decide.
181 if arguments[-1] == 'PSA_ERROR_NOT_SUPPORTED':
182 return True
Gilles Peskine45e9e732021-01-12 00:47:03 +0100183 return False
184
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100185def process_data_stanza(stanza, file_name, test_case_number):
186 """Update PSA crypto dependencies in one Mbed TLS test case.
187
188 stanza is the test case text (including the description, the dependencies,
189 the line with the function and arguments, and optionally comments). Return
190 a new stanza with an updated dependency line, preserving everything else
191 (description, comments, arguments, etc.).
192 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100193 if not stanza.lstrip('\n'):
194 # Just blank lines
195 return stanza
196 # Expect 2 or 3 non-comment lines: description, optional dependencies,
197 # function-and-arguments.
198 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
199 if len(content_matches) < 2:
200 raise Exception('Not enough content lines in paragraph {} in {}'
201 .format(test_case_number, file_name))
202 if len(content_matches) > 3:
203 raise Exception('Too many content lines in paragraph {} in {}'
204 .format(test_case_number, file_name))
205 arguments = content_matches[-1].group(0).split(':')
206 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +0100207 if keep_manual_dependencies(file_name, function_name, arguments):
208 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100209 if len(content_matches) == 2:
210 # Insert a line for the dependencies. If it turns out that there are
211 # no dependencies, we'll remove that empty line below.
212 dependencies_location = content_matches[-1].start()
213 text_before = stanza[:dependencies_location]
214 text_after = '\n' + stanza[dependencies_location:]
215 old_dependencies = []
216 dependencies_leader = 'depends_on:'
217 else:
218 dependencies_match = content_matches[-2]
219 text_before = stanza[:dependencies_match.start()]
220 text_after = stanza[dependencies_match.end():]
221 old_dependencies = dependencies_match.group(0).split(':')
222 dependencies_leader = old_dependencies.pop(0) + ':'
223 if dependencies_leader != 'depends_on:':
224 raise Exception('Next-to-last line does not start with "depends_on:"'
225 ' in paragraph {} in {}'
226 .format(test_case_number, file_name))
227 new_dependencies = updated_dependencies(file_name, function_name, arguments,
228 old_dependencies)
229 if new_dependencies:
230 stanza = (text_before +
231 dependencies_leader + ':'.join(new_dependencies) +
232 text_after)
233 else:
234 # The dependencies have become empty. Remove the depends_on: line.
235 assert text_after[0] == '\n'
236 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100237 return stanza
238
239def process_data_file(file_name, old_content):
240 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
241
242 Process old_content (the old content of the file) and return the new content.
243 """
244 old_stanzas = old_content.split('\n\n')
245 new_stanzas = [process_data_stanza(stanza, file_name, n)
246 for n, stanza in enumerate(old_stanzas, start=1)]
247 return '\n\n'.join(new_stanzas)
248
249def update_file(file_name, old_content, new_content):
250 """Update the given file with the given new content.
251
252 Replace the existing file. The previous version is renamed to *.bak.
253 Don't modify the file if the content was unchanged.
254 """
255 if new_content == old_content:
256 return
257 backup = file_name + '.bak'
258 tmp = file_name + '.tmp'
259 with open(tmp, 'w', encoding='utf-8') as new_file:
260 new_file.write(new_content)
261 os.replace(file_name, backup)
262 os.replace(tmp, file_name)
263
264def process_file(file_name):
265 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
266
267 Replace the existing file. The previous version is renamed to *.bak.
268 Don't modify the file if the content was unchanged.
269 """
270 old_content = open(file_name, encoding='utf-8').read()
271 if file_name.endswith('.data'):
272 new_content = process_data_file(file_name, old_content)
273 else:
274 raise Exception('File type not recognized: {}'
275 .format(file_name))
276 update_file(file_name, old_content, new_content)
277
278def main(args):
279 for file_name in args:
280 process_file(file_name)
281
282if __name__ == '__main__':
283 main(sys.argv[1:])