blob: 37152112be69151ee063bebf767405d6b1206056 [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_SECP256R1_ENABLED',
31 #curve#'MBEDTLS_ECP_DP_SECP384R1_ENABLED',
32 #curve#'MBEDTLS_ECP_DP_SECP521R1_ENABLED',
Gilles Peskine81dec002021-01-12 00:59:09 +010033 #curve#'MBEDTLS_ECP_DP_SECP256K1_ENABLED',
34 #curve#'MBEDTLS_ECP_DP_BP256R1_ENABLED',
35 #curve#'MBEDTLS_ECP_DP_BP384R1_ENABLED',
36 #curve#'MBEDTLS_ECP_DP_BP512R1_ENABLED',
37 #curve#'MBEDTLS_ECP_DP_CURVE25519_ENABLED',
38 #curve#'MBEDTLS_ECP_DP_CURVE448_ENABLED',
39 'MBEDTLS_ECDSA_DETERMINISTIC',
40 #'MBEDTLS_GENPRIME', #needed for RSA key generation
41 'MBEDTLS_PKCS1_V15',
42 'MBEDTLS_PKCS1_V21',
Gilles Peskine81dec002021-01-12 00:59:09 +010043
44 # Mbed TLS modules.
45 # Only modules that provide cryptographic mechanisms are listed here.
46 # Platform, data formatting, X.509 or TLS modules are omitted.
Ronald Cron9f97c6e2021-03-18 16:05:03 +010047 'MBEDTLS_AES_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010048 'MBEDTLS_BIGNUM_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010049 'MBEDTLS_CAMELLIA_C',
50 'MBEDTLS_ARIA_C',
51 'MBEDTLS_CCM_C',
52 'MBEDTLS_CHACHA20_C',
53 'MBEDTLS_CHACHAPOLY_C',
54 'MBEDTLS_CMAC_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010055 'MBEDTLS_CTR_DRBG_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010056 'MBEDTLS_ECDH_C',
57 'MBEDTLS_ECDSA_C',
58 'MBEDTLS_ECJPAKE_C',
59 'MBEDTLS_ECP_C',
60 'MBEDTLS_ENTROPY_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010061 'MBEDTLS_GCM_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010062 'MBEDTLS_HKDF_C',
63 'MBEDTLS_HMAC_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010064 'MBEDTLS_NIST_KW_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010065 'MBEDTLS_MD5_C',
66 'MBEDTLS_PKCS5_C',
67 'MBEDTLS_PKCS12_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010068 'MBEDTLS_POLY1305_C',
69 'MBEDTLS_RIPEMD160_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010070 'MBEDTLS_RSA_C',
71 'MBEDTLS_SHA1_C',
72 'MBEDTLS_SHA256_C',
73 'MBEDTLS_SHA512_C',
Gilles Peskine9bbba5e2021-01-12 00:55:55 +010074])
75
76def is_classic_dependency(dep):
77 """Whether dep is a classic dependency that PSA test cases should not use."""
78 if dep.startswith('!'):
79 dep = dep[1:]
80 return dep in CLASSIC_DEPENDENCIES
81
Gilles Peskine2d2e9242021-01-12 00:52:31 +010082def is_systematic_dependency(dep):
83 """Whether dep is a PSA dependency which is determined systematically."""
Ronald Cron6ac020d2021-03-23 17:40:47 +010084 if dep.startswith('PSA_WANT_ECC_'):
85 return False
Gilles Peskine2d2e9242021-01-12 00:52:31 +010086 return dep.startswith('PSA_WANT_')
87
Gilles Peskinefa379612021-01-12 21:14:46 +010088WITHOUT_SYSTEMATIC_DEPENDENCIES = frozenset([
Bence Szépkútia63b20d2020-12-16 11:36:46 +010089 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # only a modifier
Gilles Peskinef032fa92021-01-12 01:01:26 +010090 'PSA_ALG_ANY_HASH', # only meaningful in policies
91 'PSA_ALG_KEY_AGREEMENT', # only a way to combine algorithms
92 'PSA_ALG_TRUNCATED_MAC', # only a modifier
Gilles Peskine60b29fe2021-02-16 14:06:50 +010093 'PSA_KEY_TYPE_NONE', # not a real key type
94 'PSA_KEY_TYPE_DERIVE', # always supported, don't list it to reduce noise
95 'PSA_KEY_TYPE_RAW_DATA', # always supported, don't list it to reduce noise
Steven Cooreman2c2efa42021-02-23 09:36:42 +010096 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', #only a modifier
97 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', #only a modifier
Gilles Peskinee4f539c2021-01-12 00:58:36 +010098])
99
Gilles Peskinef032fa92021-01-12 01:01:26 +0100100SPECIAL_SYSTEMATIC_DEPENDENCIES = {
101 'PSA_ALG_ECDSA_ANY': frozenset(['PSA_WANT_ALG_ECDSA']),
102 'PSA_ALG_RSA_PKCS1V15_SIGN_RAW': frozenset(['PSA_WANT_ALG_RSA_PKCS1V15_SIGN']),
103}
104
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100105def dependencies_of_symbol(symbol):
106 """Return the dependencies for a symbol that designates a cryptographic mechanism."""
Gilles Peskinefa379612021-01-12 21:14:46 +0100107 if symbol in WITHOUT_SYSTEMATIC_DEPENDENCIES:
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100108 return frozenset()
Gilles Peskinef032fa92021-01-12 01:01:26 +0100109 if symbol in SPECIAL_SYSTEMATIC_DEPENDENCIES:
110 return SPECIAL_SYSTEMATIC_DEPENDENCIES[symbol]
Gilles Peskine20987b92021-01-12 01:11:32 +0100111 if symbol.startswith('PSA_ALG_CATEGORY_') or \
112 symbol.startswith('PSA_KEY_TYPE_CATEGORY_'):
113 # Categories are used in test data when an unsupported but plausible
114 # mechanism number needed. They have no associated dependency.
115 return frozenset()
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100116 return {symbol.replace('_', '_WANT_', 1)}
117
118def systematic_dependencies(file_name, function_name, arguments):
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100119 """List the systematically determined dependency for a test case."""
120 deps = set()
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100121
122 # Run key policy negative tests even if the algorithm to attempt performing
Ronald Cron9838dc22021-03-24 09:18:23 +0100123 # is not supported but in the case where the test is to check an
124 # incompatibility between a requested algorithm for a cryptographic
125 # operation and a key policy. In the latter, we want to filter out the
126 # cases # where PSA_ERROR_NOT_SUPPORTED is returned instead of
127 # PSA_ERROR_NOT_PERMITTED.
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100128 if function_name.endswith('_key_policy') and \
Ronald Cron9838dc22021-03-24 09:18:23 +0100129 arguments[-1].startswith('PSA_ERROR_') and \
130 arguments[-1] != ('PSA_ERROR_NOT_PERMITTED'):
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100131 arguments[-2] = ''
Gilles Peskineb51d72f2021-01-12 21:15:52 +0100132 if function_name == 'copy_fail' and \
133 arguments[-1].startswith('PSA_ERROR_'):
134 arguments[-2] = ''
135 arguments[-3] = ''
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100136
Gilles Peskine80a97082021-01-12 21:18:36 +0100137 # Storage format tests that only look at how the file is structured and
138 # don't care about the format of the key material don't depend on any
139 # cryptographic mechanisms.
140 if os.path.basename(file_name) == 'test_suite_psa_crypto_persistent_key.data' and \
141 function_name in {'format_storage_data_check',
142 'parse_storage_data_check'}:
143 return []
144
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100145 for arg in arguments:
146 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
147 deps.update(dependencies_of_symbol(symbol))
148 return sorted(deps)
149
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100150def updated_dependencies(file_name, function_name, arguments, dependencies):
151 """Rework the list of dependencies into PSA_WANT_xxx.
152
153 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
154 MBEDTLS_PKCS1_V15, etc.
155
156 Add systematic PSA_WANT_xxx dependencies based on the called function and
157 its arguments, replacing existing PSA_WANT_xxx dependencies.
158 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100159 automatic = systematic_dependencies(file_name, function_name, arguments)
160 manual = [dep for dep in dependencies
Gilles Peskine9bbba5e2021-01-12 00:55:55 +0100161 if not (is_systematic_dependency(dep) or
162 is_classic_dependency(dep))]
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100163 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100164
Gilles Peskine45e9e732021-01-12 00:47:03 +0100165def keep_manual_dependencies(file_name, function_name, arguments):
166 #pylint: disable=unused-argument
167 """Declare test functions with unusual dependencies here."""
Gilles Peskinea87e1782021-01-12 01:13:39 +0100168 # If there are no arguments, we can't do any useful work. Assume that if
169 # there are dependencies, they are warranted.
170 if not arguments:
171 return True
Gilles Peskine20987b92021-01-12 01:11:32 +0100172 # When PSA_ERROR_NOT_SUPPORTED is expected, usually, at least one of the
173 # constants mentioned in the test should not be supported. It isn't
174 # possible to determine which one in a systematic way. So let the programmer
175 # decide.
176 if arguments[-1] == 'PSA_ERROR_NOT_SUPPORTED':
177 return True
Gilles Peskine45e9e732021-01-12 00:47:03 +0100178 return False
179
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100180def process_data_stanza(stanza, file_name, test_case_number):
181 """Update PSA crypto dependencies in one Mbed TLS test case.
182
183 stanza is the test case text (including the description, the dependencies,
184 the line with the function and arguments, and optionally comments). Return
185 a new stanza with an updated dependency line, preserving everything else
186 (description, comments, arguments, etc.).
187 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100188 if not stanza.lstrip('\n'):
189 # Just blank lines
190 return stanza
191 # Expect 2 or 3 non-comment lines: description, optional dependencies,
192 # function-and-arguments.
193 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
194 if len(content_matches) < 2:
195 raise Exception('Not enough content lines in paragraph {} in {}'
196 .format(test_case_number, file_name))
197 if len(content_matches) > 3:
198 raise Exception('Too many content lines in paragraph {} in {}'
199 .format(test_case_number, file_name))
200 arguments = content_matches[-1].group(0).split(':')
201 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +0100202 if keep_manual_dependencies(file_name, function_name, arguments):
203 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100204 if len(content_matches) == 2:
205 # Insert a line for the dependencies. If it turns out that there are
206 # no dependencies, we'll remove that empty line below.
207 dependencies_location = content_matches[-1].start()
208 text_before = stanza[:dependencies_location]
209 text_after = '\n' + stanza[dependencies_location:]
210 old_dependencies = []
211 dependencies_leader = 'depends_on:'
212 else:
213 dependencies_match = content_matches[-2]
214 text_before = stanza[:dependencies_match.start()]
215 text_after = stanza[dependencies_match.end():]
216 old_dependencies = dependencies_match.group(0).split(':')
217 dependencies_leader = old_dependencies.pop(0) + ':'
218 if dependencies_leader != 'depends_on:':
219 raise Exception('Next-to-last line does not start with "depends_on:"'
220 ' in paragraph {} in {}'
221 .format(test_case_number, file_name))
222 new_dependencies = updated_dependencies(file_name, function_name, arguments,
223 old_dependencies)
224 if new_dependencies:
225 stanza = (text_before +
226 dependencies_leader + ':'.join(new_dependencies) +
227 text_after)
228 else:
229 # The dependencies have become empty. Remove the depends_on: line.
230 assert text_after[0] == '\n'
231 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100232 return stanza
233
234def process_data_file(file_name, old_content):
235 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
236
237 Process old_content (the old content of the file) and return the new content.
238 """
239 old_stanzas = old_content.split('\n\n')
240 new_stanzas = [process_data_stanza(stanza, file_name, n)
241 for n, stanza in enumerate(old_stanzas, start=1)]
242 return '\n\n'.join(new_stanzas)
243
244def update_file(file_name, old_content, new_content):
245 """Update the given file with the given new content.
246
247 Replace the existing file. The previous version is renamed to *.bak.
248 Don't modify the file if the content was unchanged.
249 """
250 if new_content == old_content:
251 return
252 backup = file_name + '.bak'
253 tmp = file_name + '.tmp'
254 with open(tmp, 'w', encoding='utf-8') as new_file:
255 new_file.write(new_content)
256 os.replace(file_name, backup)
257 os.replace(tmp, file_name)
258
259def process_file(file_name):
260 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
261
262 Replace the existing file. The previous version is renamed to *.bak.
263 Don't modify the file if the content was unchanged.
264 """
265 old_content = open(file_name, encoding='utf-8').read()
266 if file_name.endswith('.data'):
267 new_content = process_data_file(file_name, old_content)
268 else:
269 raise Exception('File type not recognized: {}'
270 .format(file_name))
271 update_file(file_name, old_content, new_content)
272
273def main(args):
274 for file_name in args:
275 process_file(file_name)
276
277if __name__ == '__main__':
278 main(sys.argv[1:])