blob: 61923d8559b721a47f72f7faa6cf1004c99e0d05 [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',
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.
Ronald Cron9f97c6e2021-03-18 16:05:03 +010064 'MBEDTLS_AES_C',
65 'MBEDTLS_ARC4_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010066 'MBEDTLS_BIGNUM_C',
67 #cipher#'MBEDTLS_BLOWFISH_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010068 'MBEDTLS_CAMELLIA_C',
69 'MBEDTLS_ARIA_C',
70 'MBEDTLS_CCM_C',
71 'MBEDTLS_CHACHA20_C',
72 'MBEDTLS_CHACHAPOLY_C',
73 'MBEDTLS_CMAC_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010074 'MBEDTLS_CTR_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010075 'MBEDTLS_DES_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010076 'MBEDTLS_DHM_C',
77 'MBEDTLS_ECDH_C',
78 'MBEDTLS_ECDSA_C',
79 'MBEDTLS_ECJPAKE_C',
80 'MBEDTLS_ECP_C',
81 'MBEDTLS_ENTROPY_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010082 'MBEDTLS_GCM_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010083 'MBEDTLS_HKDF_C',
84 'MBEDTLS_HMAC_DRBG_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010085 'MBEDTLS_NIST_KW_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010086 'MBEDTLS_MD2_C',
87 'MBEDTLS_MD4_C',
88 'MBEDTLS_MD5_C',
89 'MBEDTLS_PKCS5_C',
90 'MBEDTLS_PKCS12_C',
Ronald Cron9f97c6e2021-03-18 16:05:03 +010091 'MBEDTLS_POLY1305_C',
92 'MBEDTLS_RIPEMD160_C',
Gilles Peskine81dec002021-01-12 00:59:09 +010093 '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."""
Ronald Cron6ac020d2021-03-23 17:40:47 +0100108 if dep.startswith('PSA_WANT_ECC_'):
109 return False
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100110 return dep.startswith('PSA_WANT_')
111
Gilles Peskinefa379612021-01-12 21:14:46 +0100112WITHOUT_SYSTEMATIC_DEPENDENCIES = frozenset([
Bence Szépkútia63b20d2020-12-16 11:36:46 +0100113 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # only a modifier
Gilles Peskinef032fa92021-01-12 01:01:26 +0100114 'PSA_ALG_ANY_HASH', # only meaningful in policies
115 'PSA_ALG_KEY_AGREEMENT', # only a way to combine algorithms
116 'PSA_ALG_TRUNCATED_MAC', # only a modifier
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100117 'PSA_KEY_TYPE_NONE', # not a real key type
118 'PSA_KEY_TYPE_DERIVE', # always supported, don't list it to reduce noise
119 'PSA_KEY_TYPE_RAW_DATA', # always supported, don't list it to reduce noise
Steven Cooreman2c2efa42021-02-23 09:36:42 +0100120 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', #only a modifier
121 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', #only a modifier
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100122])
123
Gilles Peskinef032fa92021-01-12 01:01:26 +0100124SPECIAL_SYSTEMATIC_DEPENDENCIES = {
125 'PSA_ALG_ECDSA_ANY': frozenset(['PSA_WANT_ALG_ECDSA']),
126 'PSA_ALG_RSA_PKCS1V15_SIGN_RAW': frozenset(['PSA_WANT_ALG_RSA_PKCS1V15_SIGN']),
127}
128
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100129def dependencies_of_symbol(symbol):
130 """Return the dependencies for a symbol that designates a cryptographic mechanism."""
Gilles Peskinefa379612021-01-12 21:14:46 +0100131 if symbol in WITHOUT_SYSTEMATIC_DEPENDENCIES:
Gilles Peskinee4f539c2021-01-12 00:58:36 +0100132 return frozenset()
Gilles Peskinef032fa92021-01-12 01:01:26 +0100133 if symbol in SPECIAL_SYSTEMATIC_DEPENDENCIES:
134 return SPECIAL_SYSTEMATIC_DEPENDENCIES[symbol]
Gilles Peskine20987b92021-01-12 01:11:32 +0100135 if symbol.startswith('PSA_ALG_CATEGORY_') or \
136 symbol.startswith('PSA_KEY_TYPE_CATEGORY_'):
137 # Categories are used in test data when an unsupported but plausible
138 # mechanism number needed. They have no associated dependency.
139 return frozenset()
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100140 return {symbol.replace('_', '_WANT_', 1)}
141
142def systematic_dependencies(file_name, function_name, arguments):
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100143 """List the systematically determined dependency for a test case."""
144 deps = set()
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100145
146 # Run key policy negative tests even if the algorithm to attempt performing
Ronald Cron9838dc22021-03-24 09:18:23 +0100147 # is not supported but in the case where the test is to check an
148 # incompatibility between a requested algorithm for a cryptographic
149 # operation and a key policy. In the latter, we want to filter out the
150 # cases # where PSA_ERROR_NOT_SUPPORTED is returned instead of
151 # PSA_ERROR_NOT_PERMITTED.
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100152 if function_name.endswith('_key_policy') and \
Ronald Cron9838dc22021-03-24 09:18:23 +0100153 arguments[-1].startswith('PSA_ERROR_') and \
154 arguments[-1] != ('PSA_ERROR_NOT_PERMITTED'):
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100155 arguments[-2] = ''
Gilles Peskineb51d72f2021-01-12 21:15:52 +0100156 if function_name == 'copy_fail' and \
157 arguments[-1].startswith('PSA_ERROR_'):
158 arguments[-2] = ''
159 arguments[-3] = ''
Gilles Peskine72d8e0a2021-01-12 01:11:42 +0100160
Gilles Peskine80a97082021-01-12 21:18:36 +0100161 # Storage format tests that only look at how the file is structured and
162 # don't care about the format of the key material don't depend on any
163 # cryptographic mechanisms.
164 if os.path.basename(file_name) == 'test_suite_psa_crypto_persistent_key.data' and \
165 function_name in {'format_storage_data_check',
166 'parse_storage_data_check'}:
167 return []
168
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100169 for arg in arguments:
170 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
171 deps.update(dependencies_of_symbol(symbol))
172 return sorted(deps)
173
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100174def updated_dependencies(file_name, function_name, arguments, dependencies):
175 """Rework the list of dependencies into PSA_WANT_xxx.
176
177 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
178 MBEDTLS_PKCS1_V15, etc.
179
180 Add systematic PSA_WANT_xxx dependencies based on the called function and
181 its arguments, replacing existing PSA_WANT_xxx dependencies.
182 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100183 automatic = systematic_dependencies(file_name, function_name, arguments)
184 manual = [dep for dep in dependencies
Gilles Peskine9bbba5e2021-01-12 00:55:55 +0100185 if not (is_systematic_dependency(dep) or
186 is_classic_dependency(dep))]
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100187 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100188
Gilles Peskine45e9e732021-01-12 00:47:03 +0100189def keep_manual_dependencies(file_name, function_name, arguments):
190 #pylint: disable=unused-argument
191 """Declare test functions with unusual dependencies here."""
Gilles Peskinea87e1782021-01-12 01:13:39 +0100192 # If there are no arguments, we can't do any useful work. Assume that if
193 # there are dependencies, they are warranted.
194 if not arguments:
195 return True
Gilles Peskine20987b92021-01-12 01:11:32 +0100196 # When PSA_ERROR_NOT_SUPPORTED is expected, usually, at least one of the
197 # constants mentioned in the test should not be supported. It isn't
198 # possible to determine which one in a systematic way. So let the programmer
199 # decide.
200 if arguments[-1] == 'PSA_ERROR_NOT_SUPPORTED':
201 return True
Gilles Peskine45e9e732021-01-12 00:47:03 +0100202 return False
203
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100204def process_data_stanza(stanza, file_name, test_case_number):
205 """Update PSA crypto dependencies in one Mbed TLS test case.
206
207 stanza is the test case text (including the description, the dependencies,
208 the line with the function and arguments, and optionally comments). Return
209 a new stanza with an updated dependency line, preserving everything else
210 (description, comments, arguments, etc.).
211 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100212 if not stanza.lstrip('\n'):
213 # Just blank lines
214 return stanza
215 # Expect 2 or 3 non-comment lines: description, optional dependencies,
216 # function-and-arguments.
217 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
218 if len(content_matches) < 2:
219 raise Exception('Not enough content lines in paragraph {} in {}'
220 .format(test_case_number, file_name))
221 if len(content_matches) > 3:
222 raise Exception('Too many content lines in paragraph {} in {}'
223 .format(test_case_number, file_name))
224 arguments = content_matches[-1].group(0).split(':')
225 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +0100226 if keep_manual_dependencies(file_name, function_name, arguments):
227 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100228 if len(content_matches) == 2:
229 # Insert a line for the dependencies. If it turns out that there are
230 # no dependencies, we'll remove that empty line below.
231 dependencies_location = content_matches[-1].start()
232 text_before = stanza[:dependencies_location]
233 text_after = '\n' + stanza[dependencies_location:]
234 old_dependencies = []
235 dependencies_leader = 'depends_on:'
236 else:
237 dependencies_match = content_matches[-2]
238 text_before = stanza[:dependencies_match.start()]
239 text_after = stanza[dependencies_match.end():]
240 old_dependencies = dependencies_match.group(0).split(':')
241 dependencies_leader = old_dependencies.pop(0) + ':'
242 if dependencies_leader != 'depends_on:':
243 raise Exception('Next-to-last line does not start with "depends_on:"'
244 ' in paragraph {} in {}'
245 .format(test_case_number, file_name))
246 new_dependencies = updated_dependencies(file_name, function_name, arguments,
247 old_dependencies)
248 if new_dependencies:
249 stanza = (text_before +
250 dependencies_leader + ':'.join(new_dependencies) +
251 text_after)
252 else:
253 # The dependencies have become empty. Remove the depends_on: line.
254 assert text_after[0] == '\n'
255 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100256 return stanza
257
258def process_data_file(file_name, old_content):
259 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
260
261 Process old_content (the old content of the file) and return the new content.
262 """
263 old_stanzas = old_content.split('\n\n')
264 new_stanzas = [process_data_stanza(stanza, file_name, n)
265 for n, stanza in enumerate(old_stanzas, start=1)]
266 return '\n\n'.join(new_stanzas)
267
268def update_file(file_name, old_content, new_content):
269 """Update the given file with the given new content.
270
271 Replace the existing file. The previous version is renamed to *.bak.
272 Don't modify the file if the content was unchanged.
273 """
274 if new_content == old_content:
275 return
276 backup = file_name + '.bak'
277 tmp = file_name + '.tmp'
278 with open(tmp, 'w', encoding='utf-8') as new_file:
279 new_file.write(new_content)
280 os.replace(file_name, backup)
281 os.replace(tmp, file_name)
282
283def process_file(file_name):
284 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
285
286 Replace the existing file. The previous version is renamed to *.bak.
287 Don't modify the file if the content was unchanged.
288 """
289 old_content = open(file_name, encoding='utf-8').read()
290 if file_name.endswith('.data'):
291 new_content = process_data_file(file_name, old_content)
292 else:
293 raise Exception('File type not recognized: {}'
294 .format(file_name))
295 update_file(file_name, old_content, new_content)
296
297def main(args):
298 for file_name in args:
299 process_file(file_name)
300
301if __name__ == '__main__':
302 main(sys.argv[1:])