blob: 0be8ac5e4e90e8da7d8eb7f740761dc1a97455fe [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',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010056 'MBEDTLS_DES_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010057 'MBEDTLS_ECDH_C',
58 'MBEDTLS_ECDSA_C',
59 'MBEDTLS_ECJPAKE_C',
60 'MBEDTLS_ECP_C',
61 'MBEDTLS_ENTROPY_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010062 'MBEDTLS_GCM_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010063 'MBEDTLS_HKDF_C',
64 'MBEDTLS_HMAC_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010065 'MBEDTLS_NIST_KW_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010066 'MBEDTLS_MD5_C',
67 'MBEDTLS_PKCS5_C',
68 'MBEDTLS_PKCS12_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010069 'MBEDTLS_POLY1305_C',
70 'MBEDTLS_RIPEMD160_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010071 'MBEDTLS_RSA_C',
72 'MBEDTLS_SHA1_C',
73 'MBEDTLS_SHA256_C',
74 'MBEDTLS_SHA512_C',
Gilles Peskine9bbba5e2021-01-12 00:55:55 +010075])
76
77def is_classic_dependency(dep):
78 """Whether dep is a classic dependency that PSA test cases should not use."""
79 if dep.startswith('!'):
80 dep = dep[1:]
81 return dep in CLASSIC_DEPENDENCIES
82
Gilles Peskine2d2e9242021-01-12 00:52:31 +010083def is_systematic_dependency(dep):
84 """Whether dep is a PSA dependency which is determined systematically."""
Ronald Cron6ac020d2021-03-23 17:40:47 +010085 if dep.startswith('PSA_WANT_ECC_'):
86 return False
Gilles Peskine2d2e9242021-01-12 00:52:31 +010087 return dep.startswith('PSA_WANT_')
88
Gilles Peskinefa379612021-01-12 21:14:46 +010089WITHOUT_SYSTEMATIC_DEPENDENCIES = frozenset([
Bence Szépkútia63b20d2020-12-16 11:36:46 +010090 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # only a modifier
Gilles Peskinef032fa92021-01-12 01:01:26 +010091 'PSA_ALG_ANY_HASH', # only meaningful in policies
92 'PSA_ALG_KEY_AGREEMENT', # only a way to combine algorithms
93 'PSA_ALG_TRUNCATED_MAC', # only a modifier
Gilles Peskine60b29fe2021-02-16 14:06:50 +010094 'PSA_KEY_TYPE_NONE', # not a real key type
95 'PSA_KEY_TYPE_DERIVE', # always supported, don't list it to reduce noise
96 'PSA_KEY_TYPE_RAW_DATA', # always supported, don't list it to reduce noise
Steven Cooreman2c2efa42021-02-23 09:36:42 +010097 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', #only a modifier
98 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', #only a modifier
Gilles Peskinee4f539c2021-01-12 00:58:36 +010099])
100
Gilles Peskinef032fa92021-01-12 01:01:26 +0100101SPECIAL_SYSTEMATIC_DEPENDENCIES = {
102 'PSA_ALG_ECDSA_ANY': frozenset(['PSA_WANT_ALG_ECDSA']),
103 'PSA_ALG_RSA_PKCS1V15_SIGN_RAW': frozenset(['PSA_WANT_ALG_RSA_PKCS1V15_SIGN']),
104}
105
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100106def dependencies_of_symbol(symbol):
107 """Return the dependencies for a symbol that designates a cryptographic mechanism."""
Gilles Peskinefa379612021-01-12 21:14:46 +0100108 if symbol in WITHOUT_SYSTEMATIC_DEPENDENCIES:
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100109 return frozenset()
Gilles Peskinef032fa92021-01-12 01:01:26 +0100110 if symbol in SPECIAL_SYSTEMATIC_DEPENDENCIES:
111 return SPECIAL_SYSTEMATIC_DEPENDENCIES[symbol]
Gilles Peskine20987b92021-01-12 01:11:32 +0100112 if symbol.startswith('PSA_ALG_CATEGORY_') or \
113 symbol.startswith('PSA_KEY_TYPE_CATEGORY_'):
114 # Categories are used in test data when an unsupported but plausible
115 # mechanism number needed. They have no associated dependency.
116 return frozenset()
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100117 return {symbol.replace('_', '_WANT_', 1)}
118
119def systematic_dependencies(file_name, function_name, arguments):
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100120 """List the systematically determined dependency for a test case."""
121 deps = set()
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100122
123 # Run key policy negative tests even if the algorithm to attempt performing
Ronald Cron9838dc22021-03-24 09:18:23 +0100124 # is not supported but in the case where the test is to check an
125 # incompatibility between a requested algorithm for a cryptographic
126 # operation and a key policy. In the latter, we want to filter out the
127 # cases # where PSA_ERROR_NOT_SUPPORTED is returned instead of
128 # PSA_ERROR_NOT_PERMITTED.
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100129 if function_name.endswith('_key_policy') and \
Ronald Cron9838dc22021-03-24 09:18:23 +0100130 arguments[-1].startswith('PSA_ERROR_') and \
131 arguments[-1] != ('PSA_ERROR_NOT_PERMITTED'):
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100132 arguments[-2] = ''
Gilles Peskineb51d72f2021-01-12 21:15:52 +0100133 if function_name == 'copy_fail' and \
134 arguments[-1].startswith('PSA_ERROR_'):
135 arguments[-2] = ''
136 arguments[-3] = ''
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100137
Gilles Peskine80a97082021-01-12 21:18:36 +0100138 # Storage format tests that only look at how the file is structured and
139 # don't care about the format of the key material don't depend on any
140 # cryptographic mechanisms.
141 if os.path.basename(file_name) == 'test_suite_psa_crypto_persistent_key.data' and \
142 function_name in {'format_storage_data_check',
143 'parse_storage_data_check'}:
144 return []
145
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100146 for arg in arguments:
147 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
148 deps.update(dependencies_of_symbol(symbol))
149 return sorted(deps)
150
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100151def updated_dependencies(file_name, function_name, arguments, dependencies):
152 """Rework the list of dependencies into PSA_WANT_xxx.
153
154 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
155 MBEDTLS_PKCS1_V15, etc.
156
157 Add systematic PSA_WANT_xxx dependencies based on the called function and
158 its arguments, replacing existing PSA_WANT_xxx dependencies.
159 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100160 automatic = systematic_dependencies(file_name, function_name, arguments)
161 manual = [dep for dep in dependencies
Gilles Peskine9bbba5e2021-01-12 00:55:55 +0100162 if not (is_systematic_dependency(dep) or
163 is_classic_dependency(dep))]
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100164 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100165
Gilles Peskine45e9e732021-01-12 00:47:03 +0100166def keep_manual_dependencies(file_name, function_name, arguments):
167 #pylint: disable=unused-argument
168 """Declare test functions with unusual dependencies here."""
Gilles Peskinea87e1782021-01-12 01:13:39 +0100169 # If there are no arguments, we can't do any useful work. Assume that if
170 # there are dependencies, they are warranted.
171 if not arguments:
172 return True
Gilles Peskine20987b92021-01-12 01:11:32 +0100173 # When PSA_ERROR_NOT_SUPPORTED is expected, usually, at least one of the
174 # constants mentioned in the test should not be supported. It isn't
175 # possible to determine which one in a systematic way. So let the programmer
176 # decide.
177 if arguments[-1] == 'PSA_ERROR_NOT_SUPPORTED':
178 return True
Gilles Peskine45e9e732021-01-12 00:47:03 +0100179 return False
180
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100181def process_data_stanza(stanza, file_name, test_case_number):
182 """Update PSA crypto dependencies in one Mbed TLS test case.
183
184 stanza is the test case text (including the description, the dependencies,
185 the line with the function and arguments, and optionally comments). Return
186 a new stanza with an updated dependency line, preserving everything else
187 (description, comments, arguments, etc.).
188 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100189 if not stanza.lstrip('\n'):
190 # Just blank lines
191 return stanza
192 # Expect 2 or 3 non-comment lines: description, optional dependencies,
193 # function-and-arguments.
194 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
195 if len(content_matches) < 2:
196 raise Exception('Not enough content lines in paragraph {} in {}'
197 .format(test_case_number, file_name))
198 if len(content_matches) > 3:
199 raise Exception('Too many content lines in paragraph {} in {}'
200 .format(test_case_number, file_name))
201 arguments = content_matches[-1].group(0).split(':')
202 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +0100203 if keep_manual_dependencies(file_name, function_name, arguments):
204 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100205 if len(content_matches) == 2:
206 # Insert a line for the dependencies. If it turns out that there are
207 # no dependencies, we'll remove that empty line below.
208 dependencies_location = content_matches[-1].start()
209 text_before = stanza[:dependencies_location]
210 text_after = '\n' + stanza[dependencies_location:]
211 old_dependencies = []
212 dependencies_leader = 'depends_on:'
213 else:
214 dependencies_match = content_matches[-2]
215 text_before = stanza[:dependencies_match.start()]
216 text_after = stanza[dependencies_match.end():]
217 old_dependencies = dependencies_match.group(0).split(':')
218 dependencies_leader = old_dependencies.pop(0) + ':'
219 if dependencies_leader != 'depends_on:':
220 raise Exception('Next-to-last line does not start with "depends_on:"'
221 ' in paragraph {} in {}'
222 .format(test_case_number, file_name))
223 new_dependencies = updated_dependencies(file_name, function_name, arguments,
224 old_dependencies)
225 if new_dependencies:
226 stanza = (text_before +
227 dependencies_leader + ':'.join(new_dependencies) +
228 text_after)
229 else:
230 # The dependencies have become empty. Remove the depends_on: line.
231 assert text_after[0] == '\n'
232 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100233 return stanza
234
235def process_data_file(file_name, old_content):
236 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
237
238 Process old_content (the old content of the file) and return the new content.
239 """
240 old_stanzas = old_content.split('\n\n')
241 new_stanzas = [process_data_stanza(stanza, file_name, n)
242 for n, stanza in enumerate(old_stanzas, start=1)]
243 return '\n\n'.join(new_stanzas)
244
245def update_file(file_name, old_content, new_content):
246 """Update the given file with the given new content.
247
248 Replace the existing file. The previous version is renamed to *.bak.
249 Don't modify the file if the content was unchanged.
250 """
251 if new_content == old_content:
252 return
253 backup = file_name + '.bak'
254 tmp = file_name + '.tmp'
255 with open(tmp, 'w', encoding='utf-8') as new_file:
256 new_file.write(new_content)
257 os.replace(file_name, backup)
258 os.replace(tmp, file_name)
259
260def process_file(file_name):
261 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
262
263 Replace the existing file. The previous version is renamed to *.bak.
264 Don't modify the file if the content was unchanged.
265 """
266 old_content = open(file_name, encoding='utf-8').read()
267 if file_name.endswith('.data'):
268 new_content = process_data_file(file_name, old_content)
269 else:
270 raise Exception('File type not recognized: {}'
271 .format(file_name))
272 update_file(file_name, old_content, new_content)
273
274def main(args):
275 for file_name in args:
276 process_file(file_name)
277
278if __name__ == '__main__':
279 main(sys.argv[1:])