blob: e37ce3db545adc3da862d90842feea0d952d33ec [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
7# SPDX-License-Identifier: Apache-2.0
8#
9# Licensed under the Apache License, Version 2.0 (the "License"); you may
10# not use this file except in compliance with the License.
11# You may obtain a copy of the License at
12#
13# http://www.apache.org/licenses/LICENSE-2.0
14#
15# Unless required by applicable law or agreed to in writing, software
16# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18# See the License for the specific language governing permissions and
19# limitations under the License.
20
21import os
Gilles Peskine82ebaa42021-01-12 00:45:14 +010022import re
Gilles Peskinebdffaea2021-01-12 00:37:38 +010023import sys
24
Gilles Peskine9bbba5e2021-01-12 00:55:55 +010025CLASSIC_DEPENDENCIES = frozenset([
Gilles Peskine81dec002021-01-12 00:59:09 +010026 # This list is manually filtered from config.h.
27
28 # Mbed TLS feature support.
29 # Only features that affect what can be done are listed here.
30 # Options that control optimizations or alternative implementations
31 # are omitted.
32 #cipher#'MBEDTLS_CIPHER_MODE_CBC',
33 #cipher#'MBEDTLS_CIPHER_MODE_CFB',
34 #cipher#'MBEDTLS_CIPHER_MODE_CTR',
35 #cipher#'MBEDTLS_CIPHER_MODE_OFB',
36 #cipher#'MBEDTLS_CIPHER_MODE_XTS',
37 #cipher#'MBEDTLS_CIPHER_NULL_CIPHER',
38 #cipher#'MBEDTLS_CIPHER_PADDING_PKCS7',
39 #cipher#'MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS',
40 #cipher#'MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN',
41 #cipher#'MBEDTLS_CIPHER_PADDING_ZEROS',
42 #curve#'MBEDTLS_ECP_DP_SECP192R1_ENABLED',
43 #curve#'MBEDTLS_ECP_DP_SECP224R1_ENABLED',
44 #curve#'MBEDTLS_ECP_DP_SECP256R1_ENABLED',
45 #curve#'MBEDTLS_ECP_DP_SECP384R1_ENABLED',
46 #curve#'MBEDTLS_ECP_DP_SECP521R1_ENABLED',
47 #curve#'MBEDTLS_ECP_DP_SECP192K1_ENABLED',
48 #curve#'MBEDTLS_ECP_DP_SECP224K1_ENABLED',
49 #curve#'MBEDTLS_ECP_DP_SECP256K1_ENABLED',
50 #curve#'MBEDTLS_ECP_DP_BP256R1_ENABLED',
51 #curve#'MBEDTLS_ECP_DP_BP384R1_ENABLED',
52 #curve#'MBEDTLS_ECP_DP_BP512R1_ENABLED',
53 #curve#'MBEDTLS_ECP_DP_CURVE25519_ENABLED',
54 #curve#'MBEDTLS_ECP_DP_CURVE448_ENABLED',
55 'MBEDTLS_ECDSA_DETERMINISTIC',
56 #'MBEDTLS_GENPRIME', #needed for RSA key generation
57 'MBEDTLS_PKCS1_V15',
58 'MBEDTLS_PKCS1_V21',
59 'MBEDTLS_SHA512_NO_SHA384',
60
61 # Mbed TLS modules.
62 # Only modules that provide cryptographic mechanisms are listed here.
63 # Platform, data formatting, X.509 or TLS modules are omitted.
64 #cipher#'MBEDTLS_AES_C',
65 #cipher#'MBEDTLS_ARC4_C',
66 'MBEDTLS_BIGNUM_C',
67 #cipher#'MBEDTLS_BLOWFISH_C',
68 #cipher#'MBEDTLS_CAMELLIA_C',
69 #cipher#'MBEDTLS_ARIA_C',
70 #cipher#'MBEDTLS_CCM_C',
71 #cipher#'MBEDTLS_CHACHA20_C',
72 #cipher#'MBEDTLS_CHACHAPOLY_C',
73 #cipher#'MBEDTLS_CMAC_C',
74 'MBEDTLS_CTR_DRBG_C',
75 #cipher#'MBEDTLS_DES_C',
76 'MBEDTLS_DHM_C',
77 'MBEDTLS_ECDH_C',
78 'MBEDTLS_ECDSA_C',
79 'MBEDTLS_ECJPAKE_C',
80 'MBEDTLS_ECP_C',
81 'MBEDTLS_ENTROPY_C',
82 #cipher#'MBEDTLS_GCM_C',
83 'MBEDTLS_HKDF_C',
84 'MBEDTLS_HMAC_DRBG_C',
85 #cipher#'MBEDTLS_NIST_KW_C',
86 'MBEDTLS_MD2_C',
87 'MBEDTLS_MD4_C',
88 'MBEDTLS_MD5_C',
89 'MBEDTLS_PKCS5_C',
90 'MBEDTLS_PKCS12_C',
91 #cipher#'MBEDTLS_POLY1305_C',
92 #cipher#'MBEDTLS_RIPEMD160_C',
93 'MBEDTLS_RSA_C',
94 'MBEDTLS_SHA1_C',
95 'MBEDTLS_SHA256_C',
96 'MBEDTLS_SHA512_C',
97 'MBEDTLS_XTEA_C',
Gilles Peskine9bbba5e2021-01-12 00:55:55 +010098])
99
100def is_classic_dependency(dep):
101 """Whether dep is a classic dependency that PSA test cases should not use."""
102 if dep.startswith('!'):
103 dep = dep[1:]
104 return dep in CLASSIC_DEPENDENCIES
105
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100106def is_systematic_dependency(dep):
107 """Whether dep is a PSA dependency which is determined systematically."""
108 return dep.startswith('PSA_WANT_')
109
Gilles Peskinefa379612021-01-12 21:14:46 +0100110WITHOUT_SYSTEMATIC_DEPENDENCIES = frozenset([
Bence Szépkútia63b20d2020-12-16 11:36:46 +0100111 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # only a modifier
Gilles Peskinef032fa92021-01-12 01:01:26 +0100112 'PSA_ALG_ANY_HASH', # only meaningful in policies
113 'PSA_ALG_KEY_AGREEMENT', # only a way to combine algorithms
114 'PSA_ALG_TRUNCATED_MAC', # only a modifier
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100115 'PSA_KEY_TYPE_NONE', # not a real key type
116 'PSA_KEY_TYPE_DERIVE', # always supported, don't list it to reduce noise
117 'PSA_KEY_TYPE_RAW_DATA', # always supported, don't list it to reduce noise
Steven Cooreman2c2efa42021-02-23 09:36:42 +0100118 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', #only a modifier
119 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', #only a modifier
Gilles Peskinef032fa92021-01-12 01:01:26 +0100120
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100121 # Not implemented yet: cipher-related key types and algorithms.
122 # Manually extracted from crypto_values.h.
123 'PSA_KEY_TYPE_AES',
124 'PSA_KEY_TYPE_DES',
125 'PSA_KEY_TYPE_CAMELLIA',
126 'PSA_KEY_TYPE_ARC4',
127 'PSA_KEY_TYPE_CHACHA20',
128 'PSA_ALG_CBC_MAC',
129 'PSA_ALG_CMAC',
130 'PSA_ALG_STREAM_CIPHER',
131 'PSA_ALG_CTR',
132 'PSA_ALG_CFB',
133 'PSA_ALG_OFB',
134 'PSA_ALG_XTS',
135 'PSA_ALG_ECB_NO_PADDING',
136 'PSA_ALG_CBC_NO_PADDING',
137 'PSA_ALG_CBC_PKCS7',
138 'PSA_ALG_CCM',
139 'PSA_ALG_GCM',
140 'PSA_ALG_CHACHA20_POLY1305',
141])
142
Gilles Peskinef032fa92021-01-12 01:01:26 +0100143SPECIAL_SYSTEMATIC_DEPENDENCIES = {
144 'PSA_ALG_ECDSA_ANY': frozenset(['PSA_WANT_ALG_ECDSA']),
145 'PSA_ALG_RSA_PKCS1V15_SIGN_RAW': frozenset(['PSA_WANT_ALG_RSA_PKCS1V15_SIGN']),
146}
147
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100148def dependencies_of_symbol(symbol):
149 """Return the dependencies for a symbol that designates a cryptographic mechanism."""
Gilles Peskinefa379612021-01-12 21:14:46 +0100150 if symbol in WITHOUT_SYSTEMATIC_DEPENDENCIES:
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100151 return frozenset()
Gilles Peskinef032fa92021-01-12 01:01:26 +0100152 if symbol in SPECIAL_SYSTEMATIC_DEPENDENCIES:
153 return SPECIAL_SYSTEMATIC_DEPENDENCIES[symbol]
Gilles Peskine20987b92021-01-12 01:11:32 +0100154 if symbol.startswith('PSA_ALG_CATEGORY_') or \
155 symbol.startswith('PSA_KEY_TYPE_CATEGORY_'):
156 # Categories are used in test data when an unsupported but plausible
157 # mechanism number needed. They have no associated dependency.
158 return frozenset()
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100159 return {symbol.replace('_', '_WANT_', 1)}
160
161def systematic_dependencies(file_name, function_name, arguments):
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100162 """List the systematically determined dependency for a test case."""
163 deps = set()
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100164
165 # Run key policy negative tests even if the algorithm to attempt performing
166 # is not supported.
167 if function_name.endswith('_key_policy') and \
Gilles Peskine07945722021-01-12 12:57:23 +0100168 arguments[-1].startswith('PSA_ERROR_'):
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100169 arguments[-2] = ''
Gilles Peskineb51d72f2021-01-12 21:15:52 +0100170 if function_name == 'copy_fail' and \
171 arguments[-1].startswith('PSA_ERROR_'):
172 arguments[-2] = ''
173 arguments[-3] = ''
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100174
Gilles Peskine80a97082021-01-12 21:18:36 +0100175 # Storage format tests that only look at how the file is structured and
176 # don't care about the format of the key material don't depend on any
177 # cryptographic mechanisms.
178 if os.path.basename(file_name) == 'test_suite_psa_crypto_persistent_key.data' and \
179 function_name in {'format_storage_data_check',
180 'parse_storage_data_check'}:
181 return []
182
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100183 for arg in arguments:
184 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
185 deps.update(dependencies_of_symbol(symbol))
186 return sorted(deps)
187
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100188def updated_dependencies(file_name, function_name, arguments, dependencies):
189 """Rework the list of dependencies into PSA_WANT_xxx.
190
191 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
192 MBEDTLS_PKCS1_V15, etc.
193
194 Add systematic PSA_WANT_xxx dependencies based on the called function and
195 its arguments, replacing existing PSA_WANT_xxx dependencies.
196 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100197 automatic = systematic_dependencies(file_name, function_name, arguments)
198 manual = [dep for dep in dependencies
Gilles Peskine9bbba5e2021-01-12 00:55:55 +0100199 if not (is_systematic_dependency(dep) or
200 is_classic_dependency(dep))]
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100201 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100202
Gilles Peskine45e9e732021-01-12 00:47:03 +0100203def keep_manual_dependencies(file_name, function_name, arguments):
204 #pylint: disable=unused-argument
205 """Declare test functions with unusual dependencies here."""
Gilles Peskinea87e1782021-01-12 01:13:39 +0100206 # If there are no arguments, we can't do any useful work. Assume that if
207 # there are dependencies, they are warranted.
208 if not arguments:
209 return True
Gilles Peskine20987b92021-01-12 01:11:32 +0100210 # When PSA_ERROR_NOT_SUPPORTED is expected, usually, at least one of the
211 # constants mentioned in the test should not be supported. It isn't
212 # possible to determine which one in a systematic way. So let the programmer
213 # decide.
214 if arguments[-1] == 'PSA_ERROR_NOT_SUPPORTED':
215 return True
Gilles Peskine45e9e732021-01-12 00:47:03 +0100216 return False
217
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100218def process_data_stanza(stanza, file_name, test_case_number):
219 """Update PSA crypto dependencies in one Mbed TLS test case.
220
221 stanza is the test case text (including the description, the dependencies,
222 the line with the function and arguments, and optionally comments). Return
223 a new stanza with an updated dependency line, preserving everything else
224 (description, comments, arguments, etc.).
225 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100226 if not stanza.lstrip('\n'):
227 # Just blank lines
228 return stanza
229 # Expect 2 or 3 non-comment lines: description, optional dependencies,
230 # function-and-arguments.
231 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
232 if len(content_matches) < 2:
233 raise Exception('Not enough content lines in paragraph {} in {}'
234 .format(test_case_number, file_name))
235 if len(content_matches) > 3:
236 raise Exception('Too many content lines in paragraph {} in {}'
237 .format(test_case_number, file_name))
238 arguments = content_matches[-1].group(0).split(':')
239 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +0100240 if keep_manual_dependencies(file_name, function_name, arguments):
241 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100242 if len(content_matches) == 2:
243 # Insert a line for the dependencies. If it turns out that there are
244 # no dependencies, we'll remove that empty line below.
245 dependencies_location = content_matches[-1].start()
246 text_before = stanza[:dependencies_location]
247 text_after = '\n' + stanza[dependencies_location:]
248 old_dependencies = []
249 dependencies_leader = 'depends_on:'
250 else:
251 dependencies_match = content_matches[-2]
252 text_before = stanza[:dependencies_match.start()]
253 text_after = stanza[dependencies_match.end():]
254 old_dependencies = dependencies_match.group(0).split(':')
255 dependencies_leader = old_dependencies.pop(0) + ':'
256 if dependencies_leader != 'depends_on:':
257 raise Exception('Next-to-last line does not start with "depends_on:"'
258 ' in paragraph {} in {}'
259 .format(test_case_number, file_name))
260 new_dependencies = updated_dependencies(file_name, function_name, arguments,
261 old_dependencies)
262 if new_dependencies:
263 stanza = (text_before +
264 dependencies_leader + ':'.join(new_dependencies) +
265 text_after)
266 else:
267 # The dependencies have become empty. Remove the depends_on: line.
268 assert text_after[0] == '\n'
269 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100270 return stanza
271
272def process_data_file(file_name, old_content):
273 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
274
275 Process old_content (the old content of the file) and return the new content.
276 """
277 old_stanzas = old_content.split('\n\n')
278 new_stanzas = [process_data_stanza(stanza, file_name, n)
279 for n, stanza in enumerate(old_stanzas, start=1)]
280 return '\n\n'.join(new_stanzas)
281
282def update_file(file_name, old_content, new_content):
283 """Update the given file with the given new content.
284
285 Replace the existing file. The previous version is renamed to *.bak.
286 Don't modify the file if the content was unchanged.
287 """
288 if new_content == old_content:
289 return
290 backup = file_name + '.bak'
291 tmp = file_name + '.tmp'
292 with open(tmp, 'w', encoding='utf-8') as new_file:
293 new_file.write(new_content)
294 os.replace(file_name, backup)
295 os.replace(tmp, file_name)
296
297def process_file(file_name):
298 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
299
300 Replace the existing file. The previous version is renamed to *.bak.
301 Don't modify the file if the content was unchanged.
302 """
303 old_content = open(file_name, encoding='utf-8').read()
304 if file_name.endswith('.data'):
305 new_content = process_data_file(file_name, old_content)
306 else:
307 raise Exception('File type not recognized: {}'
308 .format(file_name))
309 update_file(file_name, old_content, new_content)
310
311def main(args):
312 for file_name in args:
313 process_file(file_name)
314
315if __name__ == '__main__':
316 main(sys.argv[1:])