blob: 7f4ebeb7f110eee756002ba0aa6da4cf92bd515a [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([
Bence Szépkútibb0cfeb2021-05-28 09:42:25 +020026 # This list is manually filtered from mbedtls_config.h.
Gilles Peskine81dec002021-01-12 00:59:09 +010027
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',
Gilles Peskine81dec002021-01-12 00:59:09 +010064 'MBEDTLS_BIGNUM_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010065 'MBEDTLS_CAMELLIA_C',
66 'MBEDTLS_ARIA_C',
67 'MBEDTLS_CCM_C',
68 'MBEDTLS_CHACHA20_C',
69 'MBEDTLS_CHACHAPOLY_C',
70 'MBEDTLS_CMAC_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010071 'MBEDTLS_CTR_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010072 'MBEDTLS_DES_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010073 'MBEDTLS_DHM_C',
74 'MBEDTLS_ECDH_C',
75 'MBEDTLS_ECDSA_C',
76 'MBEDTLS_ECJPAKE_C',
77 'MBEDTLS_ECP_C',
78 'MBEDTLS_ENTROPY_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010079 'MBEDTLS_GCM_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010080 'MBEDTLS_HKDF_C',
81 'MBEDTLS_HMAC_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010082 'MBEDTLS_NIST_KW_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010083 'MBEDTLS_MD5_C',
84 'MBEDTLS_PKCS5_C',
85 'MBEDTLS_PKCS12_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010086 'MBEDTLS_POLY1305_C',
87 'MBEDTLS_RIPEMD160_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010088 'MBEDTLS_RSA_C',
89 'MBEDTLS_SHA1_C',
90 'MBEDTLS_SHA256_C',
91 'MBEDTLS_SHA512_C',
Gilles Peskine9bbba5e2021-01-12 00:55:55 +010092])
93
94def is_classic_dependency(dep):
95 """Whether dep is a classic dependency that PSA test cases should not use."""
96 if dep.startswith('!'):
97 dep = dep[1:]
98 return dep in CLASSIC_DEPENDENCIES
99
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100100def is_systematic_dependency(dep):
101 """Whether dep is a PSA dependency which is determined systematically."""
Ronald Cron6ac020d2021-03-23 17:40:47 +0100102 if dep.startswith('PSA_WANT_ECC_'):
103 return False
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100104 return dep.startswith('PSA_WANT_')
105
Gilles Peskinefa379612021-01-12 21:14:46 +0100106WITHOUT_SYSTEMATIC_DEPENDENCIES = frozenset([
Bence Szépkútia63b20d2020-12-16 11:36:46 +0100107 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # only a modifier
Gilles Peskinef032fa92021-01-12 01:01:26 +0100108 'PSA_ALG_ANY_HASH', # only meaningful in policies
109 'PSA_ALG_KEY_AGREEMENT', # only a way to combine algorithms
110 'PSA_ALG_TRUNCATED_MAC', # only a modifier
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100111 'PSA_KEY_TYPE_NONE', # not a real key type
112 'PSA_KEY_TYPE_DERIVE', # always supported, don't list it to reduce noise
113 'PSA_KEY_TYPE_RAW_DATA', # always supported, don't list it to reduce noise
Steven Cooreman2c2efa42021-02-23 09:36:42 +0100114 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', #only a modifier
115 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', #only a modifier
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100116])
117
Gilles Peskinef032fa92021-01-12 01:01:26 +0100118SPECIAL_SYSTEMATIC_DEPENDENCIES = {
119 'PSA_ALG_ECDSA_ANY': frozenset(['PSA_WANT_ALG_ECDSA']),
120 'PSA_ALG_RSA_PKCS1V15_SIGN_RAW': frozenset(['PSA_WANT_ALG_RSA_PKCS1V15_SIGN']),
121}
122
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100123def dependencies_of_symbol(symbol):
124 """Return the dependencies for a symbol that designates a cryptographic mechanism."""
Gilles Peskinefa379612021-01-12 21:14:46 +0100125 if symbol in WITHOUT_SYSTEMATIC_DEPENDENCIES:
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100126 return frozenset()
Gilles Peskinef032fa92021-01-12 01:01:26 +0100127 if symbol in SPECIAL_SYSTEMATIC_DEPENDENCIES:
128 return SPECIAL_SYSTEMATIC_DEPENDENCIES[symbol]
Gilles Peskine20987b92021-01-12 01:11:32 +0100129 if symbol.startswith('PSA_ALG_CATEGORY_') or \
130 symbol.startswith('PSA_KEY_TYPE_CATEGORY_'):
131 # Categories are used in test data when an unsupported but plausible
132 # mechanism number needed. They have no associated dependency.
133 return frozenset()
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100134 return {symbol.replace('_', '_WANT_', 1)}
135
136def systematic_dependencies(file_name, function_name, arguments):
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100137 """List the systematically determined dependency for a test case."""
138 deps = set()
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100139
140 # Run key policy negative tests even if the algorithm to attempt performing
Ronald Cron9838dc22021-03-24 09:18:23 +0100141 # is not supported but in the case where the test is to check an
142 # incompatibility between a requested algorithm for a cryptographic
143 # operation and a key policy. In the latter, we want to filter out the
144 # cases # where PSA_ERROR_NOT_SUPPORTED is returned instead of
145 # PSA_ERROR_NOT_PERMITTED.
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100146 if function_name.endswith('_key_policy') and \
Ronald Cron9838dc22021-03-24 09:18:23 +0100147 arguments[-1].startswith('PSA_ERROR_') and \
148 arguments[-1] != ('PSA_ERROR_NOT_PERMITTED'):
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100149 arguments[-2] = ''
Gilles Peskineb51d72f2021-01-12 21:15:52 +0100150 if function_name == 'copy_fail' and \
151 arguments[-1].startswith('PSA_ERROR_'):
152 arguments[-2] = ''
153 arguments[-3] = ''
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100154
Gilles Peskine80a97082021-01-12 21:18:36 +0100155 # Storage format tests that only look at how the file is structured and
156 # don't care about the format of the key material don't depend on any
157 # cryptographic mechanisms.
158 if os.path.basename(file_name) == 'test_suite_psa_crypto_persistent_key.data' and \
159 function_name in {'format_storage_data_check',
160 'parse_storage_data_check'}:
161 return []
162
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100163 for arg in arguments:
164 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
165 deps.update(dependencies_of_symbol(symbol))
166 return sorted(deps)
167
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100168def updated_dependencies(file_name, function_name, arguments, dependencies):
169 """Rework the list of dependencies into PSA_WANT_xxx.
170
171 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
172 MBEDTLS_PKCS1_V15, etc.
173
174 Add systematic PSA_WANT_xxx dependencies based on the called function and
175 its arguments, replacing existing PSA_WANT_xxx dependencies.
176 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100177 automatic = systematic_dependencies(file_name, function_name, arguments)
178 manual = [dep for dep in dependencies
Gilles Peskine9bbba5e2021-01-12 00:55:55 +0100179 if not (is_systematic_dependency(dep) or
180 is_classic_dependency(dep))]
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100181 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100182
Gilles Peskine45e9e732021-01-12 00:47:03 +0100183def keep_manual_dependencies(file_name, function_name, arguments):
184 #pylint: disable=unused-argument
185 """Declare test functions with unusual dependencies here."""
Gilles Peskinea87e1782021-01-12 01:13:39 +0100186 # If there are no arguments, we can't do any useful work. Assume that if
187 # there are dependencies, they are warranted.
188 if not arguments:
189 return True
Gilles Peskine20987b92021-01-12 01:11:32 +0100190 # When PSA_ERROR_NOT_SUPPORTED is expected, usually, at least one of the
191 # constants mentioned in the test should not be supported. It isn't
192 # possible to determine which one in a systematic way. So let the programmer
193 # decide.
194 if arguments[-1] == 'PSA_ERROR_NOT_SUPPORTED':
195 return True
Gilles Peskine45e9e732021-01-12 00:47:03 +0100196 return False
197
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100198def process_data_stanza(stanza, file_name, test_case_number):
199 """Update PSA crypto dependencies in one Mbed TLS test case.
200
201 stanza is the test case text (including the description, the dependencies,
202 the line with the function and arguments, and optionally comments). Return
203 a new stanza with an updated dependency line, preserving everything else
204 (description, comments, arguments, etc.).
205 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100206 if not stanza.lstrip('\n'):
207 # Just blank lines
208 return stanza
209 # Expect 2 or 3 non-comment lines: description, optional dependencies,
210 # function-and-arguments.
211 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
212 if len(content_matches) < 2:
213 raise Exception('Not enough content lines in paragraph {} in {}'
214 .format(test_case_number, file_name))
215 if len(content_matches) > 3:
216 raise Exception('Too many content lines in paragraph {} in {}'
217 .format(test_case_number, file_name))
218 arguments = content_matches[-1].group(0).split(':')
219 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +0100220 if keep_manual_dependencies(file_name, function_name, arguments):
221 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100222 if len(content_matches) == 2:
223 # Insert a line for the dependencies. If it turns out that there are
224 # no dependencies, we'll remove that empty line below.
225 dependencies_location = content_matches[-1].start()
226 text_before = stanza[:dependencies_location]
227 text_after = '\n' + stanza[dependencies_location:]
228 old_dependencies = []
229 dependencies_leader = 'depends_on:'
230 else:
231 dependencies_match = content_matches[-2]
232 text_before = stanza[:dependencies_match.start()]
233 text_after = stanza[dependencies_match.end():]
234 old_dependencies = dependencies_match.group(0).split(':')
235 dependencies_leader = old_dependencies.pop(0) + ':'
236 if dependencies_leader != 'depends_on:':
237 raise Exception('Next-to-last line does not start with "depends_on:"'
238 ' in paragraph {} in {}'
239 .format(test_case_number, file_name))
240 new_dependencies = updated_dependencies(file_name, function_name, arguments,
241 old_dependencies)
242 if new_dependencies:
243 stanza = (text_before +
244 dependencies_leader + ':'.join(new_dependencies) +
245 text_after)
246 else:
247 # The dependencies have become empty. Remove the depends_on: line.
248 assert text_after[0] == '\n'
249 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100250 return stanza
251
252def process_data_file(file_name, old_content):
253 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
254
255 Process old_content (the old content of the file) and return the new content.
256 """
257 old_stanzas = old_content.split('\n\n')
258 new_stanzas = [process_data_stanza(stanza, file_name, n)
259 for n, stanza in enumerate(old_stanzas, start=1)]
260 return '\n\n'.join(new_stanzas)
261
262def update_file(file_name, old_content, new_content):
263 """Update the given file with the given new content.
264
265 Replace the existing file. The previous version is renamed to *.bak.
266 Don't modify the file if the content was unchanged.
267 """
268 if new_content == old_content:
269 return
270 backup = file_name + '.bak'
271 tmp = file_name + '.tmp'
272 with open(tmp, 'w', encoding='utf-8') as new_file:
273 new_file.write(new_content)
274 os.replace(file_name, backup)
275 os.replace(tmp, file_name)
276
277def process_file(file_name):
278 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
279
280 Replace the existing file. The previous version is renamed to *.bak.
281 Don't modify the file if the content was unchanged.
282 """
283 old_content = open(file_name, encoding='utf-8').read()
284 if file_name.endswith('.data'):
285 new_content = process_data_file(file_name, old_content)
286 else:
287 raise Exception('File type not recognized: {}'
288 .format(file_name))
289 update_file(file_name, old_content, new_content)
290
291def main(args):
292 for file_name in args:
293 process_file(file_name)
294
295if __name__ == '__main__':
296 main(sys.argv[1:])