blob: 8690f225b803f9c096a05d06859b05f075a5bf34 [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.
Ronald Cron9f97c6e2021-03-18 16:05:03 +010032 'MBEDTLS_CIPHER_MODE_CBC',
33 'MBEDTLS_CIPHER_MODE_CFB',
34 'MBEDTLS_CIPHER_MODE_CTR',
35 'MBEDTLS_CIPHER_MODE_OFB',
36 'MBEDTLS_CIPHER_MODE_XTS',
37 'MBEDTLS_CIPHER_NULL_CIPHER',
38 'MBEDTLS_CIPHER_PADDING_PKCS7',
39 'MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS',
40 'MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN',
41 'MBEDTLS_CIPHER_PADDING_ZEROS',
Gilles Peskine81dec002021-01-12 00:59:09 +010042 #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',
Gilles Peskine81dec002021-01-12 00:59:09 +010059
60 # Mbed TLS modules.
61 # Only modules that provide cryptographic mechanisms are listed here.
62 # Platform, data formatting, X.509 or TLS modules are omitted.
Ronald Cron9f97c6e2021-03-18 16:05:03 +010063 'MBEDTLS_AES_C',
64 'MBEDTLS_ARC4_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010065 'MBEDTLS_BIGNUM_C',
66 #cipher#'MBEDTLS_BLOWFISH_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010067 'MBEDTLS_CAMELLIA_C',
68 'MBEDTLS_ARIA_C',
69 'MBEDTLS_CCM_C',
70 'MBEDTLS_CHACHA20_C',
71 'MBEDTLS_CHACHAPOLY_C',
72 'MBEDTLS_CMAC_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010073 'MBEDTLS_CTR_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010074 'MBEDTLS_DES_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010075 'MBEDTLS_DHM_C',
76 'MBEDTLS_ECDH_C',
77 'MBEDTLS_ECDSA_C',
78 'MBEDTLS_ECJPAKE_C',
79 'MBEDTLS_ECP_C',
80 'MBEDTLS_ENTROPY_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010081 'MBEDTLS_GCM_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010082 'MBEDTLS_HKDF_C',
83 'MBEDTLS_HMAC_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010084 'MBEDTLS_NIST_KW_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010085 'MBEDTLS_MD2_C',
86 'MBEDTLS_MD4_C',
87 'MBEDTLS_MD5_C',
88 'MBEDTLS_PKCS5_C',
89 'MBEDTLS_PKCS12_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010090 'MBEDTLS_POLY1305_C',
91 'MBEDTLS_RIPEMD160_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010092 'MBEDTLS_RSA_C',
93 'MBEDTLS_SHA1_C',
94 'MBEDTLS_SHA256_C',
95 'MBEDTLS_SHA512_C',
96 'MBEDTLS_XTEA_C',
Gilles Peskine9bbba5e2021-01-12 00:55:55 +010097])
98
99def is_classic_dependency(dep):
100 """Whether dep is a classic dependency that PSA test cases should not use."""
101 if dep.startswith('!'):
102 dep = dep[1:]
103 return dep in CLASSIC_DEPENDENCIES
104
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100105def is_systematic_dependency(dep):
106 """Whether dep is a PSA dependency which is determined systematically."""
Ronald Cron6ac020d2021-03-23 17:40:47 +0100107 if dep.startswith('PSA_WANT_ECC_'):
108 return False
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100109 return dep.startswith('PSA_WANT_')
110
Gilles Peskinefa379612021-01-12 21:14:46 +0100111WITHOUT_SYSTEMATIC_DEPENDENCIES = frozenset([
Bence Szépkútia63b20d2020-12-16 11:36:46 +0100112 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # only a modifier
Gilles Peskinef032fa92021-01-12 01:01:26 +0100113 'PSA_ALG_ANY_HASH', # only meaningful in policies
114 'PSA_ALG_KEY_AGREEMENT', # only a way to combine algorithms
115 'PSA_ALG_TRUNCATED_MAC', # only a modifier
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100116 'PSA_KEY_TYPE_NONE', # not a real key type
117 'PSA_KEY_TYPE_DERIVE', # always supported, don't list it to reduce noise
118 'PSA_KEY_TYPE_RAW_DATA', # always supported, don't list it to reduce noise
Steven Cooreman2c2efa42021-02-23 09:36:42 +0100119 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', #only a modifier
120 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', #only a modifier
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100121])
122
Gilles Peskinef032fa92021-01-12 01:01:26 +0100123SPECIAL_SYSTEMATIC_DEPENDENCIES = {
124 'PSA_ALG_ECDSA_ANY': frozenset(['PSA_WANT_ALG_ECDSA']),
125 'PSA_ALG_RSA_PKCS1V15_SIGN_RAW': frozenset(['PSA_WANT_ALG_RSA_PKCS1V15_SIGN']),
126}
127
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100128def dependencies_of_symbol(symbol):
129 """Return the dependencies for a symbol that designates a cryptographic mechanism."""
Gilles Peskinefa379612021-01-12 21:14:46 +0100130 if symbol in WITHOUT_SYSTEMATIC_DEPENDENCIES:
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100131 return frozenset()
Gilles Peskinef032fa92021-01-12 01:01:26 +0100132 if symbol in SPECIAL_SYSTEMATIC_DEPENDENCIES:
133 return SPECIAL_SYSTEMATIC_DEPENDENCIES[symbol]
Gilles Peskine20987b92021-01-12 01:11:32 +0100134 if symbol.startswith('PSA_ALG_CATEGORY_') or \
135 symbol.startswith('PSA_KEY_TYPE_CATEGORY_'):
136 # Categories are used in test data when an unsupported but plausible
137 # mechanism number needed. They have no associated dependency.
138 return frozenset()
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100139 return {symbol.replace('_', '_WANT_', 1)}
140
141def systematic_dependencies(file_name, function_name, arguments):
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100142 """List the systematically determined dependency for a test case."""
143 deps = set()
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100144
145 # Run key policy negative tests even if the algorithm to attempt performing
Ronald Cron9838dc22021-03-24 09:18:23 +0100146 # is not supported but in the case where the test is to check an
147 # incompatibility between a requested algorithm for a cryptographic
148 # operation and a key policy. In the latter, we want to filter out the
149 # cases # where PSA_ERROR_NOT_SUPPORTED is returned instead of
150 # PSA_ERROR_NOT_PERMITTED.
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100151 if function_name.endswith('_key_policy') and \
Ronald Cron9838dc22021-03-24 09:18:23 +0100152 arguments[-1].startswith('PSA_ERROR_') and \
153 arguments[-1] != ('PSA_ERROR_NOT_PERMITTED'):
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100154 arguments[-2] = ''
Gilles Peskineb51d72f2021-01-12 21:15:52 +0100155 if function_name == 'copy_fail' and \
156 arguments[-1].startswith('PSA_ERROR_'):
157 arguments[-2] = ''
158 arguments[-3] = ''
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100159
Gilles Peskine80a97082021-01-12 21:18:36 +0100160 # Storage format tests that only look at how the file is structured and
161 # don't care about the format of the key material don't depend on any
162 # cryptographic mechanisms.
163 if os.path.basename(file_name) == 'test_suite_psa_crypto_persistent_key.data' and \
164 function_name in {'format_storage_data_check',
165 'parse_storage_data_check'}:
166 return []
167
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100168 for arg in arguments:
169 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
170 deps.update(dependencies_of_symbol(symbol))
171 return sorted(deps)
172
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100173def updated_dependencies(file_name, function_name, arguments, dependencies):
174 """Rework the list of dependencies into PSA_WANT_xxx.
175
176 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
177 MBEDTLS_PKCS1_V15, etc.
178
179 Add systematic PSA_WANT_xxx dependencies based on the called function and
180 its arguments, replacing existing PSA_WANT_xxx dependencies.
181 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100182 automatic = systematic_dependencies(file_name, function_name, arguments)
183 manual = [dep for dep in dependencies
Gilles Peskine9bbba5e2021-01-12 00:55:55 +0100184 if not (is_systematic_dependency(dep) or
185 is_classic_dependency(dep))]
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100186 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100187
Gilles Peskine45e9e732021-01-12 00:47:03 +0100188def keep_manual_dependencies(file_name, function_name, arguments):
189 #pylint: disable=unused-argument
190 """Declare test functions with unusual dependencies here."""
Gilles Peskinea87e1782021-01-12 01:13:39 +0100191 # If there are no arguments, we can't do any useful work. Assume that if
192 # there are dependencies, they are warranted.
193 if not arguments:
194 return True
Gilles Peskine20987b92021-01-12 01:11:32 +0100195 # When PSA_ERROR_NOT_SUPPORTED is expected, usually, at least one of the
196 # constants mentioned in the test should not be supported. It isn't
197 # possible to determine which one in a systematic way. So let the programmer
198 # decide.
199 if arguments[-1] == 'PSA_ERROR_NOT_SUPPORTED':
200 return True
Gilles Peskine45e9e732021-01-12 00:47:03 +0100201 return False
202
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100203def process_data_stanza(stanza, file_name, test_case_number):
204 """Update PSA crypto dependencies in one Mbed TLS test case.
205
206 stanza is the test case text (including the description, the dependencies,
207 the line with the function and arguments, and optionally comments). Return
208 a new stanza with an updated dependency line, preserving everything else
209 (description, comments, arguments, etc.).
210 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100211 if not stanza.lstrip('\n'):
212 # Just blank lines
213 return stanza
214 # Expect 2 or 3 non-comment lines: description, optional dependencies,
215 # function-and-arguments.
216 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
217 if len(content_matches) < 2:
218 raise Exception('Not enough content lines in paragraph {} in {}'
219 .format(test_case_number, file_name))
220 if len(content_matches) > 3:
221 raise Exception('Too many content lines in paragraph {} in {}'
222 .format(test_case_number, file_name))
223 arguments = content_matches[-1].group(0).split(':')
224 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +0100225 if keep_manual_dependencies(file_name, function_name, arguments):
226 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100227 if len(content_matches) == 2:
228 # Insert a line for the dependencies. If it turns out that there are
229 # no dependencies, we'll remove that empty line below.
230 dependencies_location = content_matches[-1].start()
231 text_before = stanza[:dependencies_location]
232 text_after = '\n' + stanza[dependencies_location:]
233 old_dependencies = []
234 dependencies_leader = 'depends_on:'
235 else:
236 dependencies_match = content_matches[-2]
237 text_before = stanza[:dependencies_match.start()]
238 text_after = stanza[dependencies_match.end():]
239 old_dependencies = dependencies_match.group(0).split(':')
240 dependencies_leader = old_dependencies.pop(0) + ':'
241 if dependencies_leader != 'depends_on:':
242 raise Exception('Next-to-last line does not start with "depends_on:"'
243 ' in paragraph {} in {}'
244 .format(test_case_number, file_name))
245 new_dependencies = updated_dependencies(file_name, function_name, arguments,
246 old_dependencies)
247 if new_dependencies:
248 stanza = (text_before +
249 dependencies_leader + ':'.join(new_dependencies) +
250 text_after)
251 else:
252 # The dependencies have become empty. Remove the depends_on: line.
253 assert text_after[0] == '\n'
254 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100255 return stanza
256
257def process_data_file(file_name, old_content):
258 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
259
260 Process old_content (the old content of the file) and return the new content.
261 """
262 old_stanzas = old_content.split('\n\n')
263 new_stanzas = [process_data_stanza(stanza, file_name, n)
264 for n, stanza in enumerate(old_stanzas, start=1)]
265 return '\n\n'.join(new_stanzas)
266
267def update_file(file_name, old_content, new_content):
268 """Update the given file with the given new content.
269
270 Replace the existing file. The previous version is renamed to *.bak.
271 Don't modify the file if the content was unchanged.
272 """
273 if new_content == old_content:
274 return
275 backup = file_name + '.bak'
276 tmp = file_name + '.tmp'
277 with open(tmp, 'w', encoding='utf-8') as new_file:
278 new_file.write(new_content)
279 os.replace(file_name, backup)
280 os.replace(tmp, file_name)
281
282def process_file(file_name):
283 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
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 old_content = open(file_name, encoding='utf-8').read()
289 if file_name.endswith('.data'):
290 new_content = process_data_file(file_name, old_content)
291 else:
292 raise Exception('File type not recognized: {}'
293 .format(file_name))
294 update_file(file_name, old_content, new_content)
295
296def main(args):
297 for file_name in args:
298 process_file(file_name)
299
300if __name__ == '__main__':
301 main(sys.argv[1:])