blob: 411cf0c2a080fe7a645948dda1f39e9e0d020c14 [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',
Gilles Peskine81dec002021-01-12 00:59:09 +010031 #curve#'MBEDTLS_ECP_DP_SECP256R1_ENABLED',
32 #curve#'MBEDTLS_ECP_DP_SECP384R1_ENABLED',
33 #curve#'MBEDTLS_ECP_DP_SECP521R1_ENABLED',
34 #curve#'MBEDTLS_ECP_DP_SECP192K1_ENABLED',
Gilles Peskine81dec002021-01-12 00:59:09 +010035 #curve#'MBEDTLS_ECP_DP_SECP256K1_ENABLED',
36 #curve#'MBEDTLS_ECP_DP_BP256R1_ENABLED',
37 #curve#'MBEDTLS_ECP_DP_BP384R1_ENABLED',
38 #curve#'MBEDTLS_ECP_DP_BP512R1_ENABLED',
39 #curve#'MBEDTLS_ECP_DP_CURVE25519_ENABLED',
40 #curve#'MBEDTLS_ECP_DP_CURVE448_ENABLED',
41 'MBEDTLS_ECDSA_DETERMINISTIC',
42 #'MBEDTLS_GENPRIME', #needed for RSA key generation
43 'MBEDTLS_PKCS1_V15',
44 'MBEDTLS_PKCS1_V21',
Gilles Peskine81dec002021-01-12 00:59:09 +010045
46 # Mbed TLS modules.
47 # Only modules that provide cryptographic mechanisms are listed here.
48 # Platform, data formatting, X.509 or TLS modules are omitted.
Ronald Cron9f97c6e2021-03-18 16:05:03 +010049 'MBEDTLS_AES_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010050 'MBEDTLS_BIGNUM_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010051 'MBEDTLS_CAMELLIA_C',
52 'MBEDTLS_ARIA_C',
53 'MBEDTLS_CCM_C',
54 'MBEDTLS_CHACHA20_C',
55 'MBEDTLS_CHACHAPOLY_C',
56 'MBEDTLS_CMAC_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010057 'MBEDTLS_CTR_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010058 'MBEDTLS_DES_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010059 'MBEDTLS_ECDH_C',
60 'MBEDTLS_ECDSA_C',
61 'MBEDTLS_ECJPAKE_C',
62 'MBEDTLS_ECP_C',
63 'MBEDTLS_ENTROPY_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010064 'MBEDTLS_GCM_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010065 'MBEDTLS_HKDF_C',
66 'MBEDTLS_HMAC_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010067 'MBEDTLS_NIST_KW_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010068 'MBEDTLS_MD5_C',
69 'MBEDTLS_PKCS5_C',
70 'MBEDTLS_PKCS12_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010071 'MBEDTLS_POLY1305_C',
72 'MBEDTLS_RIPEMD160_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010073 'MBEDTLS_RSA_C',
74 'MBEDTLS_SHA1_C',
75 'MBEDTLS_SHA256_C',
76 'MBEDTLS_SHA512_C',
Gilles Peskine9bbba5e2021-01-12 00:55:55 +010077])
78
79def is_classic_dependency(dep):
80 """Whether dep is a classic dependency that PSA test cases should not use."""
81 if dep.startswith('!'):
82 dep = dep[1:]
83 return dep in CLASSIC_DEPENDENCIES
84
Gilles Peskine2d2e9242021-01-12 00:52:31 +010085def is_systematic_dependency(dep):
86 """Whether dep is a PSA dependency which is determined systematically."""
Ronald Cron6ac020d2021-03-23 17:40:47 +010087 if dep.startswith('PSA_WANT_ECC_'):
88 return False
Gilles Peskine2d2e9242021-01-12 00:52:31 +010089 return dep.startswith('PSA_WANT_')
90
Gilles Peskinefa379612021-01-12 21:14:46 +010091WITHOUT_SYSTEMATIC_DEPENDENCIES = frozenset([
Bence Szépkútia63b20d2020-12-16 11:36:46 +010092 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # only a modifier
Gilles Peskinef032fa92021-01-12 01:01:26 +010093 'PSA_ALG_ANY_HASH', # only meaningful in policies
94 'PSA_ALG_KEY_AGREEMENT', # only a way to combine algorithms
95 'PSA_ALG_TRUNCATED_MAC', # only a modifier
Gilles Peskine60b29fe2021-02-16 14:06:50 +010096 'PSA_KEY_TYPE_NONE', # not a real key type
97 'PSA_KEY_TYPE_DERIVE', # always supported, don't list it to reduce noise
98 'PSA_KEY_TYPE_RAW_DATA', # always supported, don't list it to reduce noise
Steven Cooreman2c2efa42021-02-23 09:36:42 +010099 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', #only a modifier
100 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', #only a modifier
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100101])
102
Gilles Peskinef032fa92021-01-12 01:01:26 +0100103SPECIAL_SYSTEMATIC_DEPENDENCIES = {
104 'PSA_ALG_ECDSA_ANY': frozenset(['PSA_WANT_ALG_ECDSA']),
105 'PSA_ALG_RSA_PKCS1V15_SIGN_RAW': frozenset(['PSA_WANT_ALG_RSA_PKCS1V15_SIGN']),
106}
107
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100108def dependencies_of_symbol(symbol):
109 """Return the dependencies for a symbol that designates a cryptographic mechanism."""
Gilles Peskinefa379612021-01-12 21:14:46 +0100110 if symbol in WITHOUT_SYSTEMATIC_DEPENDENCIES:
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100111 return frozenset()
Gilles Peskinef032fa92021-01-12 01:01:26 +0100112 if symbol in SPECIAL_SYSTEMATIC_DEPENDENCIES:
113 return SPECIAL_SYSTEMATIC_DEPENDENCIES[symbol]
Gilles Peskine20987b92021-01-12 01:11:32 +0100114 if symbol.startswith('PSA_ALG_CATEGORY_') or \
115 symbol.startswith('PSA_KEY_TYPE_CATEGORY_'):
116 # Categories are used in test data when an unsupported but plausible
117 # mechanism number needed. They have no associated dependency.
118 return frozenset()
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100119 return {symbol.replace('_', '_WANT_', 1)}
120
121def systematic_dependencies(file_name, function_name, arguments):
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100122 """List the systematically determined dependency for a test case."""
123 deps = set()
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100124
125 # Run key policy negative tests even if the algorithm to attempt performing
Ronald Cron9838dc22021-03-24 09:18:23 +0100126 # is not supported but in the case where the test is to check an
127 # incompatibility between a requested algorithm for a cryptographic
128 # operation and a key policy. In the latter, we want to filter out the
129 # cases # where PSA_ERROR_NOT_SUPPORTED is returned instead of
130 # PSA_ERROR_NOT_PERMITTED.
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100131 if function_name.endswith('_key_policy') and \
Ronald Cron9838dc22021-03-24 09:18:23 +0100132 arguments[-1].startswith('PSA_ERROR_') and \
133 arguments[-1] != ('PSA_ERROR_NOT_PERMITTED'):
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100134 arguments[-2] = ''
Gilles Peskineb51d72f2021-01-12 21:15:52 +0100135 if function_name == 'copy_fail' and \
136 arguments[-1].startswith('PSA_ERROR_'):
137 arguments[-2] = ''
138 arguments[-3] = ''
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100139
Gilles Peskine80a97082021-01-12 21:18:36 +0100140 # Storage format tests that only look at how the file is structured and
141 # don't care about the format of the key material don't depend on any
142 # cryptographic mechanisms.
143 if os.path.basename(file_name) == 'test_suite_psa_crypto_persistent_key.data' and \
144 function_name in {'format_storage_data_check',
145 'parse_storage_data_check'}:
146 return []
147
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100148 for arg in arguments:
149 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
150 deps.update(dependencies_of_symbol(symbol))
151 return sorted(deps)
152
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100153def updated_dependencies(file_name, function_name, arguments, dependencies):
154 """Rework the list of dependencies into PSA_WANT_xxx.
155
156 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
157 MBEDTLS_PKCS1_V15, etc.
158
159 Add systematic PSA_WANT_xxx dependencies based on the called function and
160 its arguments, replacing existing PSA_WANT_xxx dependencies.
161 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100162 automatic = systematic_dependencies(file_name, function_name, arguments)
163 manual = [dep for dep in dependencies
Gilles Peskine9bbba5e2021-01-12 00:55:55 +0100164 if not (is_systematic_dependency(dep) or
165 is_classic_dependency(dep))]
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100166 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100167
Gilles Peskine45e9e732021-01-12 00:47:03 +0100168def keep_manual_dependencies(file_name, function_name, arguments):
169 #pylint: disable=unused-argument
170 """Declare test functions with unusual dependencies here."""
Gilles Peskinea87e1782021-01-12 01:13:39 +0100171 # If there are no arguments, we can't do any useful work. Assume that if
172 # there are dependencies, they are warranted.
173 if not arguments:
174 return True
Gilles Peskine20987b92021-01-12 01:11:32 +0100175 # When PSA_ERROR_NOT_SUPPORTED is expected, usually, at least one of the
176 # constants mentioned in the test should not be supported. It isn't
177 # possible to determine which one in a systematic way. So let the programmer
178 # decide.
179 if arguments[-1] == 'PSA_ERROR_NOT_SUPPORTED':
180 return True
Gilles Peskine45e9e732021-01-12 00:47:03 +0100181 return False
182
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100183def process_data_stanza(stanza, file_name, test_case_number):
184 """Update PSA crypto dependencies in one Mbed TLS test case.
185
186 stanza is the test case text (including the description, the dependencies,
187 the line with the function and arguments, and optionally comments). Return
188 a new stanza with an updated dependency line, preserving everything else
189 (description, comments, arguments, etc.).
190 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100191 if not stanza.lstrip('\n'):
192 # Just blank lines
193 return stanza
194 # Expect 2 or 3 non-comment lines: description, optional dependencies,
195 # function-and-arguments.
196 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
197 if len(content_matches) < 2:
198 raise Exception('Not enough content lines in paragraph {} in {}'
199 .format(test_case_number, file_name))
200 if len(content_matches) > 3:
201 raise Exception('Too many content lines in paragraph {} in {}'
202 .format(test_case_number, file_name))
203 arguments = content_matches[-1].group(0).split(':')
204 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +0100205 if keep_manual_dependencies(file_name, function_name, arguments):
206 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100207 if len(content_matches) == 2:
208 # Insert a line for the dependencies. If it turns out that there are
209 # no dependencies, we'll remove that empty line below.
210 dependencies_location = content_matches[-1].start()
211 text_before = stanza[:dependencies_location]
212 text_after = '\n' + stanza[dependencies_location:]
213 old_dependencies = []
214 dependencies_leader = 'depends_on:'
215 else:
216 dependencies_match = content_matches[-2]
217 text_before = stanza[:dependencies_match.start()]
218 text_after = stanza[dependencies_match.end():]
219 old_dependencies = dependencies_match.group(0).split(':')
220 dependencies_leader = old_dependencies.pop(0) + ':'
221 if dependencies_leader != 'depends_on:':
222 raise Exception('Next-to-last line does not start with "depends_on:"'
223 ' in paragraph {} in {}'
224 .format(test_case_number, file_name))
225 new_dependencies = updated_dependencies(file_name, function_name, arguments,
226 old_dependencies)
227 if new_dependencies:
228 stanza = (text_before +
229 dependencies_leader + ':'.join(new_dependencies) +
230 text_after)
231 else:
232 # The dependencies have become empty. Remove the depends_on: line.
233 assert text_after[0] == '\n'
234 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100235 return stanza
236
237def process_data_file(file_name, old_content):
238 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
239
240 Process old_content (the old content of the file) and return the new content.
241 """
242 old_stanzas = old_content.split('\n\n')
243 new_stanzas = [process_data_stanza(stanza, file_name, n)
244 for n, stanza in enumerate(old_stanzas, start=1)]
245 return '\n\n'.join(new_stanzas)
246
247def update_file(file_name, old_content, new_content):
248 """Update the given file with the given new content.
249
250 Replace the existing file. The previous version is renamed to *.bak.
251 Don't modify the file if the content was unchanged.
252 """
253 if new_content == old_content:
254 return
255 backup = file_name + '.bak'
256 tmp = file_name + '.tmp'
257 with open(tmp, 'w', encoding='utf-8') as new_file:
258 new_file.write(new_content)
259 os.replace(file_name, backup)
260 os.replace(tmp, file_name)
261
262def process_file(file_name):
263 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
264
265 Replace the existing file. The previous version is renamed to *.bak.
266 Don't modify the file if the content was unchanged.
267 """
268 old_content = open(file_name, encoding='utf-8').read()
269 if file_name.endswith('.data'):
270 new_content = process_data_file(file_name, old_content)
271 else:
272 raise Exception('File type not recognized: {}'
273 .format(file_name))
274 update_file(file_name, old_content, new_content)
275
276def main(args):
277 for file_name in args:
278 process_file(file_name)
279
280if __name__ == '__main__':
281 main(sys.argv[1:])