blob: 5dcd64ef3f4deb8038d5dec2acc966e26b142e64 [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([
Gilles Peskinef032fa92021-01-12 01:01:26 +0100111 'PSA_ALG_AEAD_WITH_TAG_LENGTH', # only a modifier
112 '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):
160 #pylint: disable=unused-argument
161 """List the systematically determined dependency for a test case."""
162 deps = set()
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100163
164 # Run key policy negative tests even if the algorithm to attempt performing
165 # is not supported.
166 if function_name.endswith('_key_policy') and \
Gilles Peskine07945722021-01-12 12:57:23 +0100167 arguments[-1].startswith('PSA_ERROR_'):
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100168 arguments[-2] = ''
Gilles Peskineb51d72f2021-01-12 21:15:52 +0100169 if function_name == 'copy_fail' and \
170 arguments[-1].startswith('PSA_ERROR_'):
171 arguments[-2] = ''
172 arguments[-3] = ''
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100173
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100174 for arg in arguments:
175 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
176 deps.update(dependencies_of_symbol(symbol))
177 return sorted(deps)
178
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100179def updated_dependencies(file_name, function_name, arguments, dependencies):
180 """Rework the list of dependencies into PSA_WANT_xxx.
181
182 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
183 MBEDTLS_PKCS1_V15, etc.
184
185 Add systematic PSA_WANT_xxx dependencies based on the called function and
186 its arguments, replacing existing PSA_WANT_xxx dependencies.
187 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100188 automatic = systematic_dependencies(file_name, function_name, arguments)
189 manual = [dep for dep in dependencies
Gilles Peskine9bbba5e2021-01-12 00:55:55 +0100190 if not (is_systematic_dependency(dep) or
191 is_classic_dependency(dep))]
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100192 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100193
Gilles Peskine45e9e732021-01-12 00:47:03 +0100194def keep_manual_dependencies(file_name, function_name, arguments):
195 #pylint: disable=unused-argument
196 """Declare test functions with unusual dependencies here."""
Gilles Peskinea87e1782021-01-12 01:13:39 +0100197 # If there are no arguments, we can't do any useful work. Assume that if
198 # there are dependencies, they are warranted.
199 if not arguments:
200 return True
Gilles Peskine20987b92021-01-12 01:11:32 +0100201 # When PSA_ERROR_NOT_SUPPORTED is expected, usually, at least one of the
202 # constants mentioned in the test should not be supported. It isn't
203 # possible to determine which one in a systematic way. So let the programmer
204 # decide.
205 if arguments[-1] == 'PSA_ERROR_NOT_SUPPORTED':
206 return True
Gilles Peskine45e9e732021-01-12 00:47:03 +0100207 return False
208
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100209def process_data_stanza(stanza, file_name, test_case_number):
210 """Update PSA crypto dependencies in one Mbed TLS test case.
211
212 stanza is the test case text (including the description, the dependencies,
213 the line with the function and arguments, and optionally comments). Return
214 a new stanza with an updated dependency line, preserving everything else
215 (description, comments, arguments, etc.).
216 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100217 if not stanza.lstrip('\n'):
218 # Just blank lines
219 return stanza
220 # Expect 2 or 3 non-comment lines: description, optional dependencies,
221 # function-and-arguments.
222 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
223 if len(content_matches) < 2:
224 raise Exception('Not enough content lines in paragraph {} in {}'
225 .format(test_case_number, file_name))
226 if len(content_matches) > 3:
227 raise Exception('Too many content lines in paragraph {} in {}'
228 .format(test_case_number, file_name))
229 arguments = content_matches[-1].group(0).split(':')
230 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +0100231 if keep_manual_dependencies(file_name, function_name, arguments):
232 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100233 if len(content_matches) == 2:
234 # Insert a line for the dependencies. If it turns out that there are
235 # no dependencies, we'll remove that empty line below.
236 dependencies_location = content_matches[-1].start()
237 text_before = stanza[:dependencies_location]
238 text_after = '\n' + stanza[dependencies_location:]
239 old_dependencies = []
240 dependencies_leader = 'depends_on:'
241 else:
242 dependencies_match = content_matches[-2]
243 text_before = stanza[:dependencies_match.start()]
244 text_after = stanza[dependencies_match.end():]
245 old_dependencies = dependencies_match.group(0).split(':')
246 dependencies_leader = old_dependencies.pop(0) + ':'
247 if dependencies_leader != 'depends_on:':
248 raise Exception('Next-to-last line does not start with "depends_on:"'
249 ' in paragraph {} in {}'
250 .format(test_case_number, file_name))
251 new_dependencies = updated_dependencies(file_name, function_name, arguments,
252 old_dependencies)
253 if new_dependencies:
254 stanza = (text_before +
255 dependencies_leader + ':'.join(new_dependencies) +
256 text_after)
257 else:
258 # The dependencies have become empty. Remove the depends_on: line.
259 assert text_after[0] == '\n'
260 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100261 return stanza
262
263def process_data_file(file_name, old_content):
264 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
265
266 Process old_content (the old content of the file) and return the new content.
267 """
268 old_stanzas = old_content.split('\n\n')
269 new_stanzas = [process_data_stanza(stanza, file_name, n)
270 for n, stanza in enumerate(old_stanzas, start=1)]
271 return '\n\n'.join(new_stanzas)
272
273def update_file(file_name, old_content, new_content):
274 """Update the given file with the given new content.
275
276 Replace the existing file. The previous version is renamed to *.bak.
277 Don't modify the file if the content was unchanged.
278 """
279 if new_content == old_content:
280 return
281 backup = file_name + '.bak'
282 tmp = file_name + '.tmp'
283 with open(tmp, 'w', encoding='utf-8') as new_file:
284 new_file.write(new_content)
285 os.replace(file_name, backup)
286 os.replace(tmp, file_name)
287
288def process_file(file_name):
289 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
290
291 Replace the existing file. The previous version is renamed to *.bak.
292 Don't modify the file if the content was unchanged.
293 """
294 old_content = open(file_name, encoding='utf-8').read()
295 if file_name.endswith('.data'):
296 new_content = process_data_file(file_name, old_content)
297 else:
298 raise Exception('File type not recognized: {}'
299 .format(file_name))
300 update_file(file_name, old_content, new_content)
301
302def main(args):
303 for file_name in args:
304 process_file(file_name)
305
306if __name__ == '__main__':
307 main(sys.argv[1:])