set_psa_test_dependencies.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. #!/usr/bin/env python3
  2. """Edit test cases to use PSA dependencies instead of classic dependencies.
  3. """
  4. # Copyright The Mbed TLS Contributors
  5. # SPDX-License-Identifier: Apache-2.0
  6. #
  7. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  8. # not use this file except in compliance with the License.
  9. # You may obtain a copy of the License at
  10. #
  11. # http://www.apache.org/licenses/LICENSE-2.0
  12. #
  13. # Unless required by applicable law or agreed to in writing, software
  14. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  15. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. # See the License for the specific language governing permissions and
  17. # limitations under the License.
  18. import os
  19. import re
  20. import sys
  21. CLASSIC_DEPENDENCIES = frozenset([
  22. # This list is manually filtered from config.h.
  23. # Mbed TLS feature support.
  24. # Only features that affect what can be done are listed here.
  25. # Options that control optimizations or alternative implementations
  26. # are omitted.
  27. 'MBEDTLS_CIPHER_MODE_CBC',
  28. 'MBEDTLS_CIPHER_MODE_CFB',
  29. 'MBEDTLS_CIPHER_MODE_CTR',
  30. 'MBEDTLS_CIPHER_MODE_OFB',
  31. 'MBEDTLS_CIPHER_MODE_XTS',
  32. 'MBEDTLS_CIPHER_NULL_CIPHER',
  33. 'MBEDTLS_CIPHER_PADDING_PKCS7',
  34. 'MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS',
  35. 'MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN',
  36. 'MBEDTLS_CIPHER_PADDING_ZEROS',
  37. #curve#'MBEDTLS_ECP_DP_SECP192R1_ENABLED',
  38. #curve#'MBEDTLS_ECP_DP_SECP224R1_ENABLED',
  39. #curve#'MBEDTLS_ECP_DP_SECP256R1_ENABLED',
  40. #curve#'MBEDTLS_ECP_DP_SECP384R1_ENABLED',
  41. #curve#'MBEDTLS_ECP_DP_SECP521R1_ENABLED',
  42. #curve#'MBEDTLS_ECP_DP_SECP192K1_ENABLED',
  43. #curve#'MBEDTLS_ECP_DP_SECP224K1_ENABLED',
  44. #curve#'MBEDTLS_ECP_DP_SECP256K1_ENABLED',
  45. #curve#'MBEDTLS_ECP_DP_BP256R1_ENABLED',
  46. #curve#'MBEDTLS_ECP_DP_BP384R1_ENABLED',
  47. #curve#'MBEDTLS_ECP_DP_BP512R1_ENABLED',
  48. #curve#'MBEDTLS_ECP_DP_CURVE25519_ENABLED',
  49. #curve#'MBEDTLS_ECP_DP_CURVE448_ENABLED',
  50. 'MBEDTLS_ECDSA_DETERMINISTIC',
  51. #'MBEDTLS_GENPRIME', #needed for RSA key generation
  52. 'MBEDTLS_PKCS1_V15',
  53. 'MBEDTLS_PKCS1_V21',
  54. 'MBEDTLS_SHA512_NO_SHA384',
  55. # Mbed TLS modules.
  56. # Only modules that provide cryptographic mechanisms are listed here.
  57. # Platform, data formatting, X.509 or TLS modules are omitted.
  58. 'MBEDTLS_AES_C',
  59. 'MBEDTLS_ARC4_C',
  60. 'MBEDTLS_BIGNUM_C',
  61. #cipher#'MBEDTLS_BLOWFISH_C',
  62. 'MBEDTLS_CAMELLIA_C',
  63. 'MBEDTLS_ARIA_C',
  64. 'MBEDTLS_CCM_C',
  65. 'MBEDTLS_CHACHA20_C',
  66. 'MBEDTLS_CHACHAPOLY_C',
  67. 'MBEDTLS_CMAC_C',
  68. 'MBEDTLS_CTR_DRBG_C',
  69. 'MBEDTLS_DES_C',
  70. 'MBEDTLS_DHM_C',
  71. 'MBEDTLS_ECDH_C',
  72. 'MBEDTLS_ECDSA_C',
  73. 'MBEDTLS_ECJPAKE_C',
  74. 'MBEDTLS_ECP_C',
  75. 'MBEDTLS_ENTROPY_C',
  76. 'MBEDTLS_GCM_C',
  77. 'MBEDTLS_HKDF_C',
  78. 'MBEDTLS_HMAC_DRBG_C',
  79. 'MBEDTLS_NIST_KW_C',
  80. 'MBEDTLS_MD2_C',
  81. 'MBEDTLS_MD4_C',
  82. 'MBEDTLS_MD5_C',
  83. 'MBEDTLS_PKCS5_C',
  84. 'MBEDTLS_PKCS12_C',
  85. 'MBEDTLS_POLY1305_C',
  86. 'MBEDTLS_RIPEMD160_C',
  87. 'MBEDTLS_RSA_C',
  88. 'MBEDTLS_SHA1_C',
  89. 'MBEDTLS_SHA256_C',
  90. 'MBEDTLS_SHA512_C',
  91. 'MBEDTLS_XTEA_C',
  92. ])
  93. def is_classic_dependency(dep):
  94. """Whether dep is a classic dependency that PSA test cases should not use."""
  95. if dep.startswith('!'):
  96. dep = dep[1:]
  97. return dep in CLASSIC_DEPENDENCIES
  98. def is_systematic_dependency(dep):
  99. """Whether dep is a PSA dependency which is determined systematically."""
  100. if dep.startswith('PSA_WANT_ECC_'):
  101. return False
  102. return dep.startswith('PSA_WANT_')
  103. WITHOUT_SYSTEMATIC_DEPENDENCIES = frozenset([
  104. 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # only a modifier
  105. 'PSA_ALG_ANY_HASH', # only meaningful in policies
  106. 'PSA_ALG_KEY_AGREEMENT', # only a way to combine algorithms
  107. 'PSA_ALG_TRUNCATED_MAC', # only a modifier
  108. 'PSA_KEY_TYPE_NONE', # not a real key type
  109. 'PSA_KEY_TYPE_DERIVE', # always supported, don't list it to reduce noise
  110. 'PSA_KEY_TYPE_RAW_DATA', # always supported, don't list it to reduce noise
  111. 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', #only a modifier
  112. 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', #only a modifier
  113. ])
  114. SPECIAL_SYSTEMATIC_DEPENDENCIES = {
  115. 'PSA_ALG_ECDSA_ANY': frozenset(['PSA_WANT_ALG_ECDSA']),
  116. 'PSA_ALG_RSA_PKCS1V15_SIGN_RAW': frozenset(['PSA_WANT_ALG_RSA_PKCS1V15_SIGN']),
  117. }
  118. def dependencies_of_symbol(symbol):
  119. """Return the dependencies for a symbol that designates a cryptographic mechanism."""
  120. if symbol in WITHOUT_SYSTEMATIC_DEPENDENCIES:
  121. return frozenset()
  122. if symbol in SPECIAL_SYSTEMATIC_DEPENDENCIES:
  123. return SPECIAL_SYSTEMATIC_DEPENDENCIES[symbol]
  124. if symbol.startswith('PSA_ALG_CATEGORY_') or \
  125. symbol.startswith('PSA_KEY_TYPE_CATEGORY_'):
  126. # Categories are used in test data when an unsupported but plausible
  127. # mechanism number needed. They have no associated dependency.
  128. return frozenset()
  129. return {symbol.replace('_', '_WANT_', 1)}
  130. def systematic_dependencies(file_name, function_name, arguments):
  131. """List the systematically determined dependency for a test case."""
  132. deps = set()
  133. # Run key policy negative tests even if the algorithm to attempt performing
  134. # is not supported but in the case where the test is to check an
  135. # incompatibility between a requested algorithm for a cryptographic
  136. # operation and a key policy. In the latter, we want to filter out the
  137. # cases # where PSA_ERROR_NOT_SUPPORTED is returned instead of
  138. # PSA_ERROR_NOT_PERMITTED.
  139. if function_name.endswith('_key_policy') and \
  140. arguments[-1].startswith('PSA_ERROR_') and \
  141. arguments[-1] != ('PSA_ERROR_NOT_PERMITTED'):
  142. arguments[-2] = ''
  143. if function_name == 'copy_fail' and \
  144. arguments[-1].startswith('PSA_ERROR_'):
  145. arguments[-2] = ''
  146. arguments[-3] = ''
  147. # Storage format tests that only look at how the file is structured and
  148. # don't care about the format of the key material don't depend on any
  149. # cryptographic mechanisms.
  150. if os.path.basename(file_name) == 'test_suite_psa_crypto_persistent_key.data' and \
  151. function_name in {'format_storage_data_check',
  152. 'parse_storage_data_check'}:
  153. return []
  154. for arg in arguments:
  155. for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
  156. deps.update(dependencies_of_symbol(symbol))
  157. return sorted(deps)
  158. def updated_dependencies(file_name, function_name, arguments, dependencies):
  159. """Rework the list of dependencies into PSA_WANT_xxx.
  160. Remove classic crypto dependencies such as MBEDTLS_RSA_C,
  161. MBEDTLS_PKCS1_V15, etc.
  162. Add systematic PSA_WANT_xxx dependencies based on the called function and
  163. its arguments, replacing existing PSA_WANT_xxx dependencies.
  164. """
  165. automatic = systematic_dependencies(file_name, function_name, arguments)
  166. manual = [dep for dep in dependencies
  167. if not (is_systematic_dependency(dep) or
  168. is_classic_dependency(dep))]
  169. return automatic + manual
  170. def keep_manual_dependencies(file_name, function_name, arguments):
  171. #pylint: disable=unused-argument
  172. """Declare test functions with unusual dependencies here."""
  173. # If there are no arguments, we can't do any useful work. Assume that if
  174. # there are dependencies, they are warranted.
  175. if not arguments:
  176. return True
  177. # When PSA_ERROR_NOT_SUPPORTED is expected, usually, at least one of the
  178. # constants mentioned in the test should not be supported. It isn't
  179. # possible to determine which one in a systematic way. So let the programmer
  180. # decide.
  181. if arguments[-1] == 'PSA_ERROR_NOT_SUPPORTED':
  182. return True
  183. return False
  184. def process_data_stanza(stanza, file_name, test_case_number):
  185. """Update PSA crypto dependencies in one Mbed TLS test case.
  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. """
  191. 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)
  205. if keep_manual_dependencies(file_name, function_name, arguments):
  206. return stanza
  207. 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:]
  235. return stanza
  236. def process_data_file(file_name, old_content):
  237. """Update PSA crypto dependencies in an Mbed TLS test suite data file.
  238. Process old_content (the old content of the file) and return the new content.
  239. """
  240. old_stanzas = old_content.split('\n\n')
  241. new_stanzas = [process_data_stanza(stanza, file_name, n)
  242. for n, stanza in enumerate(old_stanzas, start=1)]
  243. return '\n\n'.join(new_stanzas)
  244. def update_file(file_name, old_content, new_content):
  245. """Update the given file with the given new content.
  246. Replace the existing file. The previous version is renamed to *.bak.
  247. Don't modify the file if the content was unchanged.
  248. """
  249. if new_content == old_content:
  250. return
  251. backup = file_name + '.bak'
  252. tmp = file_name + '.tmp'
  253. with open(tmp, 'w', encoding='utf-8') as new_file:
  254. new_file.write(new_content)
  255. os.replace(file_name, backup)
  256. os.replace(tmp, file_name)
  257. def process_file(file_name):
  258. """Update PSA crypto dependencies in an Mbed TLS test suite data file.
  259. Replace the existing file. The previous version is renamed to *.bak.
  260. Don't modify the file if the content was unchanged.
  261. """
  262. old_content = open(file_name, encoding='utf-8').read()
  263. if file_name.endswith('.data'):
  264. new_content = process_data_file(file_name, old_content)
  265. else:
  266. raise Exception('File type not recognized: {}'
  267. .format(file_name))
  268. update_file(file_name, old_content, new_content)
  269. def main(args):
  270. for file_name in args:
  271. process_file(file_name)
  272. if __name__ == '__main__':
  273. main(sys.argv[1:])