blob: e3760c564f44cc050f1aa055847ca908b330f5e3 [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
115 'PSA_KEY_TYPE_NONE', # always supported
116 'PSA_KEY_TYPE_DERIVE', # always supported
117 'PSA_KEY_TYPE_RAW_DATA', # always supported
118
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100119 # Not implemented yet: cipher-related key types and algorithms.
120 # Manually extracted from crypto_values.h.
121 'PSA_KEY_TYPE_AES',
122 'PSA_KEY_TYPE_DES',
123 'PSA_KEY_TYPE_CAMELLIA',
124 'PSA_KEY_TYPE_ARC4',
125 'PSA_KEY_TYPE_CHACHA20',
126 'PSA_ALG_CBC_MAC',
127 'PSA_ALG_CMAC',
128 'PSA_ALG_STREAM_CIPHER',
129 'PSA_ALG_CTR',
130 'PSA_ALG_CFB',
131 'PSA_ALG_OFB',
132 'PSA_ALG_XTS',
133 'PSA_ALG_ECB_NO_PADDING',
134 'PSA_ALG_CBC_NO_PADDING',
135 'PSA_ALG_CBC_PKCS7',
136 'PSA_ALG_CCM',
137 'PSA_ALG_GCM',
138 'PSA_ALG_CHACHA20_POLY1305',
139])
140
Gilles Peskinef032fa92021-01-12 01:01:26 +0100141SPECIAL_SYSTEMATIC_DEPENDENCIES = {
142 'PSA_ALG_ECDSA_ANY': frozenset(['PSA_WANT_ALG_ECDSA']),
143 'PSA_ALG_RSA_PKCS1V15_SIGN_RAW': frozenset(['PSA_WANT_ALG_RSA_PKCS1V15_SIGN']),
144}
145
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100146def dependencies_of_symbol(symbol):
147 """Return the dependencies for a symbol that designates a cryptographic mechanism."""
Gilles Peskinefa379612021-01-12 21:14:46 +0100148 if symbol in WITHOUT_SYSTEMATIC_DEPENDENCIES:
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100149 return frozenset()
Gilles Peskinef032fa92021-01-12 01:01:26 +0100150 if symbol in SPECIAL_SYSTEMATIC_DEPENDENCIES:
151 return SPECIAL_SYSTEMATIC_DEPENDENCIES[symbol]
Gilles Peskine20987b92021-01-12 01:11:32 +0100152 if symbol.startswith('PSA_ALG_CATEGORY_') or \
153 symbol.startswith('PSA_KEY_TYPE_CATEGORY_'):
154 # Categories are used in test data when an unsupported but plausible
155 # mechanism number needed. They have no associated dependency.
156 return frozenset()
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100157 return {symbol.replace('_', '_WANT_', 1)}
158
159def systematic_dependencies(file_name, function_name, arguments):
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100160 """List the systematically determined dependency for a test case."""
161 deps = set()
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100162
163 # Run key policy negative tests even if the algorithm to attempt performing
164 # is not supported.
165 if function_name.endswith('_key_policy') and \
Gilles Peskine07945722021-01-12 12:57:23 +0100166 arguments[-1].startswith('PSA_ERROR_'):
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100167 arguments[-2] = ''
Gilles Peskineb51d72f2021-01-12 21:15:52 +0100168 if function_name == 'copy_fail' and \
169 arguments[-1].startswith('PSA_ERROR_'):
170 arguments[-2] = ''
171 arguments[-3] = ''
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100172
Gilles Peskine80a97082021-01-12 21:18:36 +0100173 # Storage format tests that only look at how the file is structured and
174 # don't care about the format of the key material don't depend on any
175 # cryptographic mechanisms.
176 if os.path.basename(file_name) == 'test_suite_psa_crypto_persistent_key.data' and \
177 function_name in {'format_storage_data_check',
178 'parse_storage_data_check'}:
179 return []
180
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100181 for arg in arguments:
182 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
183 deps.update(dependencies_of_symbol(symbol))
184 return sorted(deps)
185
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100186def updated_dependencies(file_name, function_name, arguments, dependencies):
187 """Rework the list of dependencies into PSA_WANT_xxx.
188
189 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
190 MBEDTLS_PKCS1_V15, etc.
191
192 Add systematic PSA_WANT_xxx dependencies based on the called function and
193 its arguments, replacing existing PSA_WANT_xxx dependencies.
194 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100195 automatic = systematic_dependencies(file_name, function_name, arguments)
196 manual = [dep for dep in dependencies
Gilles Peskine9bbba5e2021-01-12 00:55:55 +0100197 if not (is_systematic_dependency(dep) or
198 is_classic_dependency(dep))]
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100199 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100200
Gilles Peskine45e9e732021-01-12 00:47:03 +0100201def keep_manual_dependencies(file_name, function_name, arguments):
202 #pylint: disable=unused-argument
203 """Declare test functions with unusual dependencies here."""
Gilles Peskinea87e1782021-01-12 01:13:39 +0100204 # If there are no arguments, we can't do any useful work. Assume that if
205 # there are dependencies, they are warranted.
206 if not arguments:
207 return True
Gilles Peskine20987b92021-01-12 01:11:32 +0100208 # When PSA_ERROR_NOT_SUPPORTED is expected, usually, at least one of the
209 # constants mentioned in the test should not be supported. It isn't
210 # possible to determine which one in a systematic way. So let the programmer
211 # decide.
212 if arguments[-1] == 'PSA_ERROR_NOT_SUPPORTED':
213 return True
Gilles Peskine45e9e732021-01-12 00:47:03 +0100214 return False
215
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100216def process_data_stanza(stanza, file_name, test_case_number):
217 """Update PSA crypto dependencies in one Mbed TLS test case.
218
219 stanza is the test case text (including the description, the dependencies,
220 the line with the function and arguments, and optionally comments). Return
221 a new stanza with an updated dependency line, preserving everything else
222 (description, comments, arguments, etc.).
223 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100224 if not stanza.lstrip('\n'):
225 # Just blank lines
226 return stanza
227 # Expect 2 or 3 non-comment lines: description, optional dependencies,
228 # function-and-arguments.
229 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
230 if len(content_matches) < 2:
231 raise Exception('Not enough content lines in paragraph {} in {}'
232 .format(test_case_number, file_name))
233 if len(content_matches) > 3:
234 raise Exception('Too many content lines in paragraph {} in {}'
235 .format(test_case_number, file_name))
236 arguments = content_matches[-1].group(0).split(':')
237 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +0100238 if keep_manual_dependencies(file_name, function_name, arguments):
239 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100240 if len(content_matches) == 2:
241 # Insert a line for the dependencies. If it turns out that there are
242 # no dependencies, we'll remove that empty line below.
243 dependencies_location = content_matches[-1].start()
244 text_before = stanza[:dependencies_location]
245 text_after = '\n' + stanza[dependencies_location:]
246 old_dependencies = []
247 dependencies_leader = 'depends_on:'
248 else:
249 dependencies_match = content_matches[-2]
250 text_before = stanza[:dependencies_match.start()]
251 text_after = stanza[dependencies_match.end():]
252 old_dependencies = dependencies_match.group(0).split(':')
253 dependencies_leader = old_dependencies.pop(0) + ':'
254 if dependencies_leader != 'depends_on:':
255 raise Exception('Next-to-last line does not start with "depends_on:"'
256 ' in paragraph {} in {}'
257 .format(test_case_number, file_name))
258 new_dependencies = updated_dependencies(file_name, function_name, arguments,
259 old_dependencies)
260 if new_dependencies:
261 stanza = (text_before +
262 dependencies_leader + ':'.join(new_dependencies) +
263 text_after)
264 else:
265 # The dependencies have become empty. Remove the depends_on: line.
266 assert text_after[0] == '\n'
267 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100268 return stanza
269
270def process_data_file(file_name, old_content):
271 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
272
273 Process old_content (the old content of the file) and return the new content.
274 """
275 old_stanzas = old_content.split('\n\n')
276 new_stanzas = [process_data_stanza(stanza, file_name, n)
277 for n, stanza in enumerate(old_stanzas, start=1)]
278 return '\n\n'.join(new_stanzas)
279
280def update_file(file_name, old_content, new_content):
281 """Update the given file with the given new content.
282
283 Replace the existing file. The previous version is renamed to *.bak.
284 Don't modify the file if the content was unchanged.
285 """
286 if new_content == old_content:
287 return
288 backup = file_name + '.bak'
289 tmp = file_name + '.tmp'
290 with open(tmp, 'w', encoding='utf-8') as new_file:
291 new_file.write(new_content)
292 os.replace(file_name, backup)
293 os.replace(tmp, file_name)
294
295def process_file(file_name):
296 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
297
298 Replace the existing file. The previous version is renamed to *.bak.
299 Don't modify the file if the content was unchanged.
300 """
301 old_content = open(file_name, encoding='utf-8').read()
302 if file_name.endswith('.data'):
303 new_content = process_data_file(file_name, old_content)
304 else:
305 raise Exception('File type not recognized: {}'
306 .format(file_name))
307 update_file(file_name, old_content, new_content)
308
309def main(args):
310 for file_name in args:
311 process_file(file_name)
312
313if __name__ == '__main__':
314 main(sys.argv[1:])