blob: 7f913cdd11524bc313fd8702492f96ed66ebc0f2 [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 Peskinee4f539c2021-01-12 00:58:36 +0100110OMITTED_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 Peskinee4f539c2021-01-12 00:58:36 +0100148 if symbol in OMITTED_SYSTEMATIC_DEPENDENCIES:
149 return frozenset()
Gilles Peskinef032fa92021-01-12 01:01:26 +0100150 if symbol in SPECIAL_SYSTEMATIC_DEPENDENCIES:
151 return SPECIAL_SYSTEMATIC_DEPENDENCIES[symbol]
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100152 return {symbol.replace('_', '_WANT_', 1)}
153
154def systematic_dependencies(file_name, function_name, arguments):
155 #pylint: disable=unused-argument
156 """List the systematically determined dependency for a test case."""
157 deps = set()
158 for arg in arguments:
159 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
160 deps.update(dependencies_of_symbol(symbol))
161 return sorted(deps)
162
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100163def updated_dependencies(file_name, function_name, arguments, dependencies):
164 """Rework the list of dependencies into PSA_WANT_xxx.
165
166 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
167 MBEDTLS_PKCS1_V15, etc.
168
169 Add systematic PSA_WANT_xxx dependencies based on the called function and
170 its arguments, replacing existing PSA_WANT_xxx dependencies.
171 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100172 automatic = systematic_dependencies(file_name, function_name, arguments)
173 manual = [dep for dep in dependencies
Gilles Peskine9bbba5e2021-01-12 00:55:55 +0100174 if not (is_systematic_dependency(dep) or
175 is_classic_dependency(dep))]
Gilles Peskine2d2e9242021-01-12 00:52:31 +0100176 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100177
Gilles Peskine45e9e732021-01-12 00:47:03 +0100178def keep_manual_dependencies(file_name, function_name, arguments):
179 #pylint: disable=unused-argument
180 """Declare test functions with unusual dependencies here."""
181 return False
182
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100183def process_data_stanza(stanza, file_name, test_case_number):
184 """Update PSA crypto dependencies in one Mbed TLS test case.
185
186 stanza is the test case text (including the description, the dependencies,
187 the line with the function and arguments, and optionally comments). Return
188 a new stanza with an updated dependency line, preserving everything else
189 (description, comments, arguments, etc.).
190 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100191 if not stanza.lstrip('\n'):
192 # Just blank lines
193 return stanza
194 # Expect 2 or 3 non-comment lines: description, optional dependencies,
195 # function-and-arguments.
196 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
197 if len(content_matches) < 2:
198 raise Exception('Not enough content lines in paragraph {} in {}'
199 .format(test_case_number, file_name))
200 if len(content_matches) > 3:
201 raise Exception('Too many content lines in paragraph {} in {}'
202 .format(test_case_number, file_name))
203 arguments = content_matches[-1].group(0).split(':')
204 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +0100205 if keep_manual_dependencies(file_name, function_name, arguments):
206 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +0100207 if len(content_matches) == 2:
208 # Insert a line for the dependencies. If it turns out that there are
209 # no dependencies, we'll remove that empty line below.
210 dependencies_location = content_matches[-1].start()
211 text_before = stanza[:dependencies_location]
212 text_after = '\n' + stanza[dependencies_location:]
213 old_dependencies = []
214 dependencies_leader = 'depends_on:'
215 else:
216 dependencies_match = content_matches[-2]
217 text_before = stanza[:dependencies_match.start()]
218 text_after = stanza[dependencies_match.end():]
219 old_dependencies = dependencies_match.group(0).split(':')
220 dependencies_leader = old_dependencies.pop(0) + ':'
221 if dependencies_leader != 'depends_on:':
222 raise Exception('Next-to-last line does not start with "depends_on:"'
223 ' in paragraph {} in {}'
224 .format(test_case_number, file_name))
225 new_dependencies = updated_dependencies(file_name, function_name, arguments,
226 old_dependencies)
227 if new_dependencies:
228 stanza = (text_before +
229 dependencies_leader + ':'.join(new_dependencies) +
230 text_after)
231 else:
232 # The dependencies have become empty. Remove the depends_on: line.
233 assert text_after[0] == '\n'
234 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100235 return stanza
236
237def process_data_file(file_name, old_content):
238 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
239
240 Process old_content (the old content of the file) and return the new content.
241 """
242 old_stanzas = old_content.split('\n\n')
243 new_stanzas = [process_data_stanza(stanza, file_name, n)
244 for n, stanza in enumerate(old_stanzas, start=1)]
245 return '\n\n'.join(new_stanzas)
246
247def update_file(file_name, old_content, new_content):
248 """Update the given file with the given new content.
249
250 Replace the existing file. The previous version is renamed to *.bak.
251 Don't modify the file if the content was unchanged.
252 """
253 if new_content == old_content:
254 return
255 backup = file_name + '.bak'
256 tmp = file_name + '.tmp'
257 with open(tmp, 'w', encoding='utf-8') as new_file:
258 new_file.write(new_content)
259 os.replace(file_name, backup)
260 os.replace(tmp, file_name)
261
262def process_file(file_name):
263 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
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 old_content = open(file_name, encoding='utf-8').read()
269 if file_name.endswith('.data'):
270 new_content = process_data_file(file_name, old_content)
271 else:
272 raise Exception('File type not recognized: {}'
273 .format(file_name))
274 update_file(file_name, old_content, new_content)
275
276def main(args):
277 for file_name in args:
278 process_file(file_name)
279
280if __name__ == '__main__':
281 main(sys.argv[1:])