blob: f68dfcb72b11dd1c8d22531a23cee8b21c9c1c74 [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_DHM_C',
62 'MBEDTLS_ECDH_C',
63 'MBEDTLS_ECDSA_C',
64 'MBEDTLS_ECJPAKE_C',
65 'MBEDTLS_ECP_C',
66 'MBEDTLS_ENTROPY_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010067 'MBEDTLS_GCM_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010068 'MBEDTLS_HKDF_C',
69 'MBEDTLS_HMAC_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010070 'MBEDTLS_NIST_KW_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010071 'MBEDTLS_MD5_C',
72 'MBEDTLS_PKCS5_C',
73 'MBEDTLS_PKCS12_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010074 'MBEDTLS_POLY1305_C',
75 'MBEDTLS_RIPEMD160_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010076 'MBEDTLS_RSA_C',
77 'MBEDTLS_SHA1_C',
78 'MBEDTLS_SHA256_C',
79 'MBEDTLS_SHA512_C',
Gilles Peskine9bbba5e2021-01-12 00:55:55 +010080])
81
82def is_classic_dependency(dep):
83 """Whether dep is a classic dependency that PSA test cases should not use."""
84 if dep.startswith('!'):
85 dep = dep[1:]
86 return dep in CLASSIC_DEPENDENCIES
87
Gilles Peskine2d2e9242021-01-12 00:52:31 +010088def is_systematic_dependency(dep):
89 """Whether dep is a PSA dependency which is determined systematically."""
Ronald Cron6ac020d2021-03-23 17:40:47 +010090 if dep.startswith('PSA_WANT_ECC_'):
91 return False
Gilles Peskine2d2e9242021-01-12 00:52:31 +010092 return dep.startswith('PSA_WANT_')
93
Gilles Peskinefa379612021-01-12 21:14:46 +010094WITHOUT_SYSTEMATIC_DEPENDENCIES = frozenset([
Bence Szépkútia63b20d2020-12-16 11:36:46 +010095 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # only a modifier
Gilles Peskinef032fa92021-01-12 01:01:26 +010096 'PSA_ALG_ANY_HASH', # only meaningful in policies
97 'PSA_ALG_KEY_AGREEMENT', # only a way to combine algorithms
98 'PSA_ALG_TRUNCATED_MAC', # only a modifier
Gilles Peskine60b29fe2021-02-16 14:06:50 +010099 'PSA_KEY_TYPE_NONE', # not a real key type
100 'PSA_KEY_TYPE_DERIVE', # always supported, don't list it to reduce noise
101 'PSA_KEY_TYPE_RAW_DATA', # always supported, don't list it to reduce noise
Steven Cooreman2c2efa42021-02-23 09:36:42 +0100102 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', #only a modifier
103 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', #only a modifier
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100104])
105
Gilles Peskinef032fa92021-01-12 01:01:26 +0100106SPECIAL_SYSTEMATIC_DEPENDENCIES = {
107 'PSA_ALG_ECDSA_ANY': frozenset(['PSA_WANT_ALG_ECDSA']),
108 'PSA_ALG_RSA_PKCS1V15_SIGN_RAW': frozenset(['PSA_WANT_ALG_RSA_PKCS1V15_SIGN']),
109}
110
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100111def dependencies_of_symbol(symbol):
112 """Return the dependencies for a symbol that designates a cryptographic mechanism."""
Gilles Peskinefa379612021-01-12 21:14:46 +0100113 if symbol in WITHOUT_SYSTEMATIC_DEPENDENCIES:
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100114 return frozenset()
Gilles Peskinef032fa92021-01-12 01:01:26 +0100115 if symbol in SPECIAL_SYSTEMATIC_DEPENDENCIES:
116 return SPECIAL_SYSTEMATIC_DEPENDENCIES[symbol]
Gilles Peskine20987b92021-01-12 01:11:32 +0100117 if symbol.startswith('PSA_ALG_CATEGORY_') or \
118 symbol.startswith('PSA_KEY_TYPE_CATEGORY_'):
119 # Categories are used in test data when an unsupported but plausible
120 # mechanism number needed. They have no associated dependency.
121 return frozenset()
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100122 return {symbol.replace('_', '_WANT_', 1)}
123
124def systematic_dependencies(file_name, function_name, arguments):
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100125 """List the systematically determined dependency for a test case."""
126 deps = set()
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100127
128 # Run key policy negative tests even if the algorithm to attempt performing
Ronald Cron9838dc22021-03-24 09:18:23 +0100129 # is not supported but in the case where the test is to check an
130 # incompatibility between a requested algorithm for a cryptographic
131 # operation and a key policy. In the latter, we want to filter out the
132 # cases # where PSA_ERROR_NOT_SUPPORTED is returned instead of
133 # PSA_ERROR_NOT_PERMITTED.
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100134 if function_name.endswith('_key_policy') and \
Ronald Cron9838dc22021-03-24 09:18:23 +0100135 arguments[-1].startswith('PSA_ERROR_') and \
136 arguments[-1] != ('PSA_ERROR_NOT_PERMITTED'):
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100137 arguments[-2] = ''
Gilles Peskineb51d72f2021-01-12 21:15:52 +0100138 if function_name == 'copy_fail' and \
139 arguments[-1].startswith('PSA_ERROR_'):
140 arguments[-2] = ''
141 arguments[-3] = ''
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100142
Gilles Peskine80a97082021-01-12 21:18:36 +0100143 # Storage format tests that only look at how the file is structured and
144 # don't care about the format of the key material don't depend on any
145 # cryptographic mechanisms.
146 if os.path.basename(file_name) == 'test_suite_psa_crypto_persistent_key.data' and \
147 function_name in {'format_storage_data_check',
148 'parse_storage_data_check'}:
149 return []
150
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100151 for arg in arguments:
152 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
153 deps.update(dependencies_of_symbol(symbol))
154 return sorted(deps)
155
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100156def updated_dependencies(file_name, function_name, arguments, dependencies):
157 """Rework the list of dependencies into PSA_WANT_xxx.
158
159 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
160 MBEDTLS_PKCS1_V15, etc.
161
162 Add systematic PSA_WANT_xxx dependencies based on the called function and
163 its arguments, replacing existing PSA_WANT_xxx dependencies.
164 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100165 automatic = systematic_dependencies(file_name, function_name, arguments)
166 manual = [dep for dep in dependencies
Gilles Peskine9bbba5e2021-01-12 00:55:55 +0100167 if not (is_systematic_dependency(dep) or
168 is_classic_dependency(dep))]
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100169 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100170
Gilles Peskine45e9e732021-01-12 00:47:03 +0100171def keep_manual_dependencies(file_name, function_name, arguments):
172 #pylint: disable=unused-argument
173 """Declare test functions with unusual dependencies here."""
Gilles Peskinea87e1782021-01-12 01:13:39 +0100174 # If there are no arguments, we can't do any useful work. Assume that if
175 # there are dependencies, they are warranted.
176 if not arguments:
177 return True
Gilles Peskine20987b92021-01-12 01:11:32 +0100178 # When PSA_ERROR_NOT_SUPPORTED is expected, usually, at least one of the
179 # constants mentioned in the test should not be supported. It isn't
180 # possible to determine which one in a systematic way. So let the programmer
181 # decide.
182 if arguments[-1] == 'PSA_ERROR_NOT_SUPPORTED':
183 return True
Gilles Peskine45e9e732021-01-12 00:47:03 +0100184 return False
185
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100186def process_data_stanza(stanza, file_name, test_case_number):
187 """Update PSA crypto dependencies in one Mbed TLS test case.
188
189 stanza is the test case text (including the description, the dependencies,
190 the line with the function and arguments, and optionally comments). Return
191 a new stanza with an updated dependency line, preserving everything else
192 (description, comments, arguments, etc.).
193 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100194 if not stanza.lstrip('\n'):
195 # Just blank lines
196 return stanza
197 # Expect 2 or 3 non-comment lines: description, optional dependencies,
198 # function-and-arguments.
199 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
200 if len(content_matches) < 2:
201 raise Exception('Not enough content lines in paragraph {} in {}'
202 .format(test_case_number, file_name))
203 if len(content_matches) > 3:
204 raise Exception('Too many content lines in paragraph {} in {}'
205 .format(test_case_number, file_name))
206 arguments = content_matches[-1].group(0).split(':')
207 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +0100208 if keep_manual_dependencies(file_name, function_name, arguments):
209 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100210 if len(content_matches) == 2:
211 # Insert a line for the dependencies. If it turns out that there are
212 # no dependencies, we'll remove that empty line below.
213 dependencies_location = content_matches[-1].start()
214 text_before = stanza[:dependencies_location]
215 text_after = '\n' + stanza[dependencies_location:]
216 old_dependencies = []
217 dependencies_leader = 'depends_on:'
218 else:
219 dependencies_match = content_matches[-2]
220 text_before = stanza[:dependencies_match.start()]
221 text_after = stanza[dependencies_match.end():]
222 old_dependencies = dependencies_match.group(0).split(':')
223 dependencies_leader = old_dependencies.pop(0) + ':'
224 if dependencies_leader != 'depends_on:':
225 raise Exception('Next-to-last line does not start with "depends_on:"'
226 ' in paragraph {} in {}'
227 .format(test_case_number, file_name))
228 new_dependencies = updated_dependencies(file_name, function_name, arguments,
229 old_dependencies)
230 if new_dependencies:
231 stanza = (text_before +
232 dependencies_leader + ':'.join(new_dependencies) +
233 text_after)
234 else:
235 # The dependencies have become empty. Remove the depends_on: line.
236 assert text_after[0] == '\n'
237 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100238 return stanza
239
240def process_data_file(file_name, old_content):
241 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
242
243 Process old_content (the old content of the file) and return the new content.
244 """
245 old_stanzas = old_content.split('\n\n')
246 new_stanzas = [process_data_stanza(stanza, file_name, n)
247 for n, stanza in enumerate(old_stanzas, start=1)]
248 return '\n\n'.join(new_stanzas)
249
250def update_file(file_name, old_content, new_content):
251 """Update the given file with the given new content.
252
253 Replace the existing file. The previous version is renamed to *.bak.
254 Don't modify the file if the content was unchanged.
255 """
256 if new_content == old_content:
257 return
258 backup = file_name + '.bak'
259 tmp = file_name + '.tmp'
260 with open(tmp, 'w', encoding='utf-8') as new_file:
261 new_file.write(new_content)
262 os.replace(file_name, backup)
263 os.replace(tmp, file_name)
264
265def process_file(file_name):
266 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
267
268 Replace the existing file. The previous version is renamed to *.bak.
269 Don't modify the file if the content was unchanged.
270 """
271 old_content = open(file_name, encoding='utf-8').read()
272 if file_name.endswith('.data'):
273 new_content = process_data_file(file_name, old_content)
274 else:
275 raise Exception('File type not recognized: {}'
276 .format(file_name))
277 update_file(file_name, old_content, new_content)
278
279def main(args):
280 for file_name in args:
281 process_file(file_name)
282
283if __name__ == '__main__':
284 main(sys.argv[1:])