blob: df7cea839e26a7718fe49dfb2527d9e526d47192 [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 Rodgman7ff79652023-11-03 12:04:52 +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([
Gilles Peskine81dec002021-01-12 00:59:09 +010014 # This list is manually filtered from config.h.
15
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',
47 'MBEDTLS_SHA512_NO_SHA384',
48
49 # Mbed TLS modules.
50 # Only modules that provide cryptographic mechanisms are listed here.
51 # Platform, data formatting, X.509 or TLS modules are omitted.
Ronald Cron9f97c6e2021-03-18 16:05:03 +010052 'MBEDTLS_AES_C',
53 'MBEDTLS_ARC4_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010054 'MBEDTLS_BIGNUM_C',
55 #cipher#'MBEDTLS_BLOWFISH_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010056 'MBEDTLS_CAMELLIA_C',
57 'MBEDTLS_ARIA_C',
58 'MBEDTLS_CCM_C',
59 'MBEDTLS_CHACHA20_C',
60 'MBEDTLS_CHACHAPOLY_C',
61 'MBEDTLS_CMAC_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010062 'MBEDTLS_CTR_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010063 'MBEDTLS_DES_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010064 'MBEDTLS_DHM_C',
65 'MBEDTLS_ECDH_C',
66 'MBEDTLS_ECDSA_C',
67 'MBEDTLS_ECJPAKE_C',
68 'MBEDTLS_ECP_C',
69 'MBEDTLS_ENTROPY_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010070 'MBEDTLS_GCM_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010071 'MBEDTLS_HKDF_C',
72 'MBEDTLS_HMAC_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010073 'MBEDTLS_NIST_KW_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010074 'MBEDTLS_MD2_C',
75 'MBEDTLS_MD4_C',
76 'MBEDTLS_MD5_C',
77 'MBEDTLS_PKCS5_C',
78 'MBEDTLS_PKCS12_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010079 'MBEDTLS_POLY1305_C',
80 'MBEDTLS_RIPEMD160_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010081 'MBEDTLS_RSA_C',
82 'MBEDTLS_SHA1_C',
83 'MBEDTLS_SHA256_C',
84 'MBEDTLS_SHA512_C',
85 'MBEDTLS_XTEA_C',
Gilles Peskine9bbba5e2021-01-12 00:55:55 +010086])
87
88def is_classic_dependency(dep):
89 """Whether dep is a classic dependency that PSA test cases should not use."""
90 if dep.startswith('!'):
91 dep = dep[1:]
92 return dep in CLASSIC_DEPENDENCIES
93
Gilles Peskine2d2e9242021-01-12 00:52:31 +010094def is_systematic_dependency(dep):
95 """Whether dep is a PSA dependency which is determined systematically."""
Ronald Cron6ac020d2021-03-23 17:40:47 +010096 if dep.startswith('PSA_WANT_ECC_'):
97 return False
Gilles Peskine2d2e9242021-01-12 00:52:31 +010098 return dep.startswith('PSA_WANT_')
99
Gilles Peskinefa379612021-01-12 21:14:46 +0100100WITHOUT_SYSTEMATIC_DEPENDENCIES = frozenset([
Bence Szépkútia63b20d2020-12-16 11:36:46 +0100101 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # only a modifier
Gilles Peskinef032fa92021-01-12 01:01:26 +0100102 'PSA_ALG_ANY_HASH', # only meaningful in policies
103 'PSA_ALG_KEY_AGREEMENT', # only a way to combine algorithms
104 'PSA_ALG_TRUNCATED_MAC', # only a modifier
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100105 'PSA_KEY_TYPE_NONE', # not a real key type
106 'PSA_KEY_TYPE_DERIVE', # always supported, don't list it to reduce noise
107 'PSA_KEY_TYPE_RAW_DATA', # always supported, don't list it to reduce noise
Steven Cooreman2c2efa42021-02-23 09:36:42 +0100108 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', #only a modifier
109 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', #only a modifier
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100110])
111
Gilles Peskinef032fa92021-01-12 01:01:26 +0100112SPECIAL_SYSTEMATIC_DEPENDENCIES = {
113 'PSA_ALG_ECDSA_ANY': frozenset(['PSA_WANT_ALG_ECDSA']),
114 'PSA_ALG_RSA_PKCS1V15_SIGN_RAW': frozenset(['PSA_WANT_ALG_RSA_PKCS1V15_SIGN']),
115}
116
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100117def dependencies_of_symbol(symbol):
118 """Return the dependencies for a symbol that designates a cryptographic mechanism."""
Gilles Peskinefa379612021-01-12 21:14:46 +0100119 if symbol in WITHOUT_SYSTEMATIC_DEPENDENCIES:
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100120 return frozenset()
Gilles Peskinef032fa92021-01-12 01:01:26 +0100121 if symbol in SPECIAL_SYSTEMATIC_DEPENDENCIES:
122 return SPECIAL_SYSTEMATIC_DEPENDENCIES[symbol]
Gilles Peskine20987b92021-01-12 01:11:32 +0100123 if symbol.startswith('PSA_ALG_CATEGORY_') or \
124 symbol.startswith('PSA_KEY_TYPE_CATEGORY_'):
125 # Categories are used in test data when an unsupported but plausible
126 # mechanism number needed. They have no associated dependency.
127 return frozenset()
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100128 return {symbol.replace('_', '_WANT_', 1)}
129
130def systematic_dependencies(file_name, function_name, arguments):
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100131 """List the systematically determined dependency for a test case."""
132 deps = set()
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100133
134 # Run key policy negative tests even if the algorithm to attempt performing
Ronald Cron9838dc22021-03-24 09:18:23 +0100135 # is not supported but in the case where the test is to check an
136 # incompatibility between a requested algorithm for a cryptographic
137 # operation and a key policy. In the latter, we want to filter out the
138 # cases # where PSA_ERROR_NOT_SUPPORTED is returned instead of
139 # PSA_ERROR_NOT_PERMITTED.
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100140 if function_name.endswith('_key_policy') and \
Ronald Cron9838dc22021-03-24 09:18:23 +0100141 arguments[-1].startswith('PSA_ERROR_') and \
142 arguments[-1] != ('PSA_ERROR_NOT_PERMITTED'):
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100143 arguments[-2] = ''
Gilles Peskineb51d72f2021-01-12 21:15:52 +0100144 if function_name == 'copy_fail' and \
145 arguments[-1].startswith('PSA_ERROR_'):
146 arguments[-2] = ''
147 arguments[-3] = ''
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100148
Gilles Peskine80a97082021-01-12 21:18:36 +0100149 # Storage format tests that only look at how the file is structured and
150 # don't care about the format of the key material don't depend on any
151 # cryptographic mechanisms.
152 if os.path.basename(file_name) == 'test_suite_psa_crypto_persistent_key.data' and \
153 function_name in {'format_storage_data_check',
154 'parse_storage_data_check'}:
155 return []
156
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100157 for arg in arguments:
158 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
159 deps.update(dependencies_of_symbol(symbol))
160 return sorted(deps)
161
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100162def updated_dependencies(file_name, function_name, arguments, dependencies):
163 """Rework the list of dependencies into PSA_WANT_xxx.
164
165 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
166 MBEDTLS_PKCS1_V15, etc.
167
168 Add systematic PSA_WANT_xxx dependencies based on the called function and
169 its arguments, replacing existing PSA_WANT_xxx dependencies.
170 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100171 automatic = systematic_dependencies(file_name, function_name, arguments)
172 manual = [dep for dep in dependencies
Gilles Peskine9bbba5e2021-01-12 00:55:55 +0100173 if not (is_systematic_dependency(dep) or
174 is_classic_dependency(dep))]
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100175 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100176
Gilles Peskine45e9e732021-01-12 00:47:03 +0100177def keep_manual_dependencies(file_name, function_name, arguments):
178 #pylint: disable=unused-argument
179 """Declare test functions with unusual dependencies here."""
Gilles Peskinea87e1782021-01-12 01:13:39 +0100180 # If there are no arguments, we can't do any useful work. Assume that if
181 # there are dependencies, they are warranted.
182 if not arguments:
183 return True
Gilles Peskine20987b92021-01-12 01:11:32 +0100184 # When PSA_ERROR_NOT_SUPPORTED is expected, usually, at least one of the
185 # constants mentioned in the test should not be supported. It isn't
186 # possible to determine which one in a systematic way. So let the programmer
187 # decide.
188 if arguments[-1] == 'PSA_ERROR_NOT_SUPPORTED':
189 return True
Gilles Peskine45e9e732021-01-12 00:47:03 +0100190 return False
191
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100192def process_data_stanza(stanza, file_name, test_case_number):
193 """Update PSA crypto dependencies in one Mbed TLS test case.
194
195 stanza is the test case text (including the description, the dependencies,
196 the line with the function and arguments, and optionally comments). Return
197 a new stanza with an updated dependency line, preserving everything else
198 (description, comments, arguments, etc.).
199 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100200 if not stanza.lstrip('\n'):
201 # Just blank lines
202 return stanza
203 # Expect 2 or 3 non-comment lines: description, optional dependencies,
204 # function-and-arguments.
205 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
206 if len(content_matches) < 2:
207 raise Exception('Not enough content lines in paragraph {} in {}'
208 .format(test_case_number, file_name))
209 if len(content_matches) > 3:
210 raise Exception('Too many content lines in paragraph {} in {}'
211 .format(test_case_number, file_name))
212 arguments = content_matches[-1].group(0).split(':')
213 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +0100214 if keep_manual_dependencies(file_name, function_name, arguments):
215 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100216 if len(content_matches) == 2:
217 # Insert a line for the dependencies. If it turns out that there are
218 # no dependencies, we'll remove that empty line below.
219 dependencies_location = content_matches[-1].start()
220 text_before = stanza[:dependencies_location]
221 text_after = '\n' + stanza[dependencies_location:]
222 old_dependencies = []
223 dependencies_leader = 'depends_on:'
224 else:
225 dependencies_match = content_matches[-2]
226 text_before = stanza[:dependencies_match.start()]
227 text_after = stanza[dependencies_match.end():]
228 old_dependencies = dependencies_match.group(0).split(':')
229 dependencies_leader = old_dependencies.pop(0) + ':'
230 if dependencies_leader != 'depends_on:':
231 raise Exception('Next-to-last line does not start with "depends_on:"'
232 ' in paragraph {} in {}'
233 .format(test_case_number, file_name))
234 new_dependencies = updated_dependencies(file_name, function_name, arguments,
235 old_dependencies)
236 if new_dependencies:
237 stanza = (text_before +
238 dependencies_leader + ':'.join(new_dependencies) +
239 text_after)
240 else:
241 # The dependencies have become empty. Remove the depends_on: line.
242 assert text_after[0] == '\n'
243 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100244 return stanza
245
246def process_data_file(file_name, old_content):
247 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
248
249 Process old_content (the old content of the file) and return the new content.
250 """
251 old_stanzas = old_content.split('\n\n')
252 new_stanzas = [process_data_stanza(stanza, file_name, n)
253 for n, stanza in enumerate(old_stanzas, start=1)]
254 return '\n\n'.join(new_stanzas)
255
256def update_file(file_name, old_content, new_content):
257 """Update the given file with the given new content.
258
259 Replace the existing file. The previous version is renamed to *.bak.
260 Don't modify the file if the content was unchanged.
261 """
262 if new_content == old_content:
263 return
264 backup = file_name + '.bak'
265 tmp = file_name + '.tmp'
266 with open(tmp, 'w', encoding='utf-8') as new_file:
267 new_file.write(new_content)
268 os.replace(file_name, backup)
269 os.replace(tmp, file_name)
270
271def process_file(file_name):
272 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
273
274 Replace the existing file. The previous version is renamed to *.bak.
275 Don't modify the file if the content was unchanged.
276 """
277 old_content = open(file_name, encoding='utf-8').read()
278 if file_name.endswith('.data'):
279 new_content = process_data_file(file_name, old_content)
280 else:
281 raise Exception('File type not recognized: {}'
282 .format(file_name))
283 update_file(file_name, old_content, new_content)
284
285def main(args):
286 for file_name in args:
287 process_file(file_name)
288
289if __name__ == '__main__':
290 main(sys.argv[1:])