generate_psa_tests.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767
  1. #!/usr/bin/env python3
  2. """Generate test data for PSA cryptographic mechanisms.
  3. With no arguments, generate all test data. With non-option arguments,
  4. generate only the specified files.
  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. import argparse
  21. import os
  22. import re
  23. import sys
  24. from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional, TypeVar
  25. import scripts_path # pylint: disable=unused-import
  26. from mbedtls_dev import crypto_knowledge
  27. from mbedtls_dev import macro_collector
  28. from mbedtls_dev import psa_storage
  29. from mbedtls_dev import test_case
  30. T = TypeVar('T') #pylint: disable=invalid-name
  31. def psa_want_symbol(name: str) -> str:
  32. """Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
  33. if name.startswith('PSA_'):
  34. return name[:4] + 'WANT_' + name[4:]
  35. else:
  36. raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
  37. def finish_family_dependency(dep: str, bits: int) -> str:
  38. """Finish dep if it's a family dependency symbol prefix.
  39. A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
  40. qualified by the key size. If dep is such a symbol, finish it by adjusting
  41. the prefix and appending the key size. Other symbols are left unchanged.
  42. """
  43. return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
  44. def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
  45. """Finish any family dependency symbol prefixes.
  46. Apply `finish_family_dependency` to each element of `dependencies`.
  47. """
  48. return [finish_family_dependency(dep, bits) for dep in dependencies]
  49. SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
  50. 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
  51. 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
  52. 'PSA_ALG_ANY_HASH', # only in policies
  53. 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
  54. 'PSA_ALG_KEY_AGREEMENT', # chaining
  55. 'PSA_ALG_TRUNCATED_MAC', # modifier
  56. ])
  57. def automatic_dependencies(*expressions: str) -> List[str]:
  58. """Infer dependencies of a test case by looking for PSA_xxx symbols.
  59. The arguments are strings which should be C expressions. Do not use
  60. string literals or comments as this function is not smart enough to
  61. skip them.
  62. """
  63. used = set()
  64. for expr in expressions:
  65. used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
  66. used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
  67. return sorted(psa_want_symbol(name) for name in used)
  68. # A temporary hack: at the time of writing, not all dependency symbols
  69. # are implemented yet. Skip test cases for which the dependency symbols are
  70. # not available. Once all dependency symbols are available, this hack must
  71. # be removed so that a bug in the dependency symbols proprely leads to a test
  72. # failure.
  73. def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
  74. return frozenset(symbol
  75. for line in open(filename)
  76. for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
  77. _implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
  78. def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
  79. global _implemented_dependencies #pylint: disable=global-statement,invalid-name
  80. if _implemented_dependencies is None:
  81. _implemented_dependencies = \
  82. read_implemented_dependencies('include/psa/crypto_config.h')
  83. if not all((dep.lstrip('!') in _implemented_dependencies or 'PSA_WANT' not in dep)
  84. for dep in dependencies):
  85. dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
  86. class Information:
  87. """Gather information about PSA constructors."""
  88. def __init__(self) -> None:
  89. self.constructors = self.read_psa_interface()
  90. @staticmethod
  91. def remove_unwanted_macros(
  92. constructors: macro_collector.PSAMacroEnumerator
  93. ) -> None:
  94. # Mbed TLS doesn't support finite-field DH yet and will not support
  95. # finite-field DSA. Don't attempt to generate any related test case.
  96. constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
  97. constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
  98. constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
  99. constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
  100. def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
  101. """Return the list of known key types, algorithms, etc."""
  102. constructors = macro_collector.InputsForTest()
  103. header_file_names = ['include/psa/crypto_values.h',
  104. 'include/psa/crypto_extra.h']
  105. test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
  106. for header_file_name in header_file_names:
  107. constructors.parse_header(header_file_name)
  108. for test_cases in test_suites:
  109. constructors.parse_test_cases(test_cases)
  110. self.remove_unwanted_macros(constructors)
  111. constructors.gather_arguments()
  112. return constructors
  113. def test_case_for_key_type_not_supported(
  114. verb: str, key_type: str, bits: int,
  115. dependencies: List[str],
  116. *args: str,
  117. param_descr: str = ''
  118. ) -> test_case.TestCase:
  119. """Return one test case exercising a key creation method
  120. for an unsupported key type or size.
  121. """
  122. hack_dependencies_not_implemented(dependencies)
  123. tc = test_case.TestCase()
  124. short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
  125. adverb = 'not' if dependencies else 'never'
  126. if param_descr:
  127. adverb = param_descr + ' ' + adverb
  128. tc.set_description('PSA {} {} {}-bit {} supported'
  129. .format(verb, short_key_type, bits, adverb))
  130. tc.set_dependencies(dependencies)
  131. tc.set_function(verb + '_not_supported')
  132. tc.set_arguments([key_type] + list(args))
  133. return tc
  134. class NotSupported:
  135. """Generate test cases for when something is not supported."""
  136. def __init__(self, info: Information) -> None:
  137. self.constructors = info.constructors
  138. ALWAYS_SUPPORTED = frozenset([
  139. 'PSA_KEY_TYPE_DERIVE',
  140. 'PSA_KEY_TYPE_RAW_DATA',
  141. ])
  142. def test_cases_for_key_type_not_supported(
  143. self,
  144. kt: crypto_knowledge.KeyType,
  145. param: Optional[int] = None,
  146. param_descr: str = '',
  147. ) -> Iterator[test_case.TestCase]:
  148. """Return test cases exercising key creation when the given type is unsupported.
  149. If param is present and not None, emit test cases conditioned on this
  150. parameter not being supported. If it is absent or None, emit test cases
  151. conditioned on the base type not being supported.
  152. """
  153. if kt.name in self.ALWAYS_SUPPORTED:
  154. # Don't generate test cases for key types that are always supported.
  155. # They would be skipped in all configurations, which is noise.
  156. return
  157. import_dependencies = [('!' if param is None else '') +
  158. psa_want_symbol(kt.name)]
  159. if kt.params is not None:
  160. import_dependencies += [('!' if param == i else '') +
  161. psa_want_symbol(sym)
  162. for i, sym in enumerate(kt.params)]
  163. if kt.name.endswith('_PUBLIC_KEY'):
  164. generate_dependencies = []
  165. else:
  166. generate_dependencies = import_dependencies
  167. for bits in kt.sizes_to_test():
  168. yield test_case_for_key_type_not_supported(
  169. 'import', kt.expression, bits,
  170. finish_family_dependencies(import_dependencies, bits),
  171. test_case.hex_string(kt.key_material(bits)),
  172. param_descr=param_descr,
  173. )
  174. if not generate_dependencies and param is not None:
  175. # If generation is impossible for this key type, rather than
  176. # supported or not depending on implementation capabilities,
  177. # only generate the test case once.
  178. continue
  179. # For public key we expect that key generation fails with
  180. # INVALID_ARGUMENT. It is handled by KeyGenerate class.
  181. if not kt.name.endswith('_PUBLIC_KEY'):
  182. yield test_case_for_key_type_not_supported(
  183. 'generate', kt.expression, bits,
  184. finish_family_dependencies(generate_dependencies, bits),
  185. str(bits),
  186. param_descr=param_descr,
  187. )
  188. # To be added: derive
  189. ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
  190. 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
  191. def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
  192. """Generate test cases that exercise the creation of keys of unsupported types."""
  193. for key_type in sorted(self.constructors.key_types):
  194. if key_type in self.ECC_KEY_TYPES:
  195. continue
  196. kt = crypto_knowledge.KeyType(key_type)
  197. yield from self.test_cases_for_key_type_not_supported(kt)
  198. for curve_family in sorted(self.constructors.ecc_curves):
  199. for constr in self.ECC_KEY_TYPES:
  200. kt = crypto_knowledge.KeyType(constr, [curve_family])
  201. yield from self.test_cases_for_key_type_not_supported(
  202. kt, param_descr='type')
  203. yield from self.test_cases_for_key_type_not_supported(
  204. kt, 0, param_descr='curve')
  205. def test_case_for_key_generation(
  206. key_type: str, bits: int,
  207. dependencies: List[str],
  208. *args: str,
  209. result: str = ''
  210. ) -> test_case.TestCase:
  211. """Return one test case exercising a key generation.
  212. """
  213. hack_dependencies_not_implemented(dependencies)
  214. tc = test_case.TestCase()
  215. short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
  216. tc.set_description('PSA {} {}-bit'
  217. .format(short_key_type, bits))
  218. tc.set_dependencies(dependencies)
  219. tc.set_function('generate_key')
  220. tc.set_arguments([key_type] + list(args) + [result])
  221. return tc
  222. class KeyGenerate:
  223. """Generate positive and negative (invalid argument) test cases for key generation."""
  224. def __init__(self, info: Information) -> None:
  225. self.constructors = info.constructors
  226. ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
  227. 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
  228. @staticmethod
  229. def test_cases_for_key_type_key_generation(
  230. kt: crypto_knowledge.KeyType
  231. ) -> Iterator[test_case.TestCase]:
  232. """Return test cases exercising key generation.
  233. All key types can be generated except for public keys. For public key
  234. PSA_ERROR_INVALID_ARGUMENT status is expected.
  235. """
  236. result = 'PSA_SUCCESS'
  237. import_dependencies = [psa_want_symbol(kt.name)]
  238. if kt.params is not None:
  239. import_dependencies += [psa_want_symbol(sym)
  240. for i, sym in enumerate(kt.params)]
  241. if kt.name.endswith('_PUBLIC_KEY'):
  242. # The library checks whether the key type is a public key generically,
  243. # before it reaches a point where it needs support for the specific key
  244. # type, so it returns INVALID_ARGUMENT for unsupported public key types.
  245. generate_dependencies = []
  246. result = 'PSA_ERROR_INVALID_ARGUMENT'
  247. else:
  248. generate_dependencies = import_dependencies
  249. if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
  250. generate_dependencies.append("MBEDTLS_GENPRIME")
  251. for bits in kt.sizes_to_test():
  252. yield test_case_for_key_generation(
  253. kt.expression, bits,
  254. finish_family_dependencies(generate_dependencies, bits),
  255. str(bits),
  256. result
  257. )
  258. def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
  259. """Generate test cases that exercise the generation of keys."""
  260. for key_type in sorted(self.constructors.key_types):
  261. if key_type in self.ECC_KEY_TYPES:
  262. continue
  263. kt = crypto_knowledge.KeyType(key_type)
  264. yield from self.test_cases_for_key_type_key_generation(kt)
  265. for curve_family in sorted(self.constructors.ecc_curves):
  266. for constr in self.ECC_KEY_TYPES:
  267. kt = crypto_knowledge.KeyType(constr, [curve_family])
  268. yield from self.test_cases_for_key_type_key_generation(kt)
  269. class StorageKey(psa_storage.Key):
  270. """Representation of a key for storage format testing."""
  271. IMPLICIT_USAGE_FLAGS = {
  272. 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
  273. 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
  274. } #type: Dict[str, str]
  275. """Mapping of usage flags to the flags that they imply."""
  276. def __init__(
  277. self,
  278. usage: str,
  279. without_implicit_usage: Optional[bool] = False,
  280. **kwargs
  281. ) -> None:
  282. """Prepare to generate a key.
  283. * `usage` : The usage flags used for the key.
  284. * `without_implicit_usage`: Flag to defide to apply the usage extension
  285. """
  286. super().__init__(usage=usage, **kwargs)
  287. if not without_implicit_usage:
  288. for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
  289. if self.usage.value() & psa_storage.Expr(flag).value() and \
  290. self.usage.value() & psa_storage.Expr(implicit).value() == 0:
  291. self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
  292. class StorageTestData(StorageKey):
  293. """Representation of test case data for storage format testing."""
  294. def __init__(
  295. self,
  296. description: str,
  297. expected_usage: Optional[str] = None,
  298. **kwargs
  299. ) -> None:
  300. """Prepare to generate test data
  301. * `description` : used for the the test case names
  302. * `expected_usage`: the usage flags generated as the expected usage flags
  303. in the test cases. CAn differ from the usage flags
  304. stored in the keys because of the usage flags extension.
  305. """
  306. super().__init__(**kwargs)
  307. self.description = description #type: str
  308. self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
  309. class StorageFormat:
  310. """Storage format stability test cases."""
  311. def __init__(self, info: Information, version: int, forward: bool) -> None:
  312. """Prepare to generate test cases for storage format stability.
  313. * `info`: information about the API. See the `Information` class.
  314. * `version`: the storage format version to generate test cases for.
  315. * `forward`: if true, generate forward compatibility test cases which
  316. save a key and check that its representation is as intended. Otherwise
  317. generate backward compatibility test cases which inject a key
  318. representation and check that it can be read and used.
  319. """
  320. self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
  321. self.version = version #type: int
  322. self.forward = forward #type: bool
  323. def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
  324. """Construct a storage format test case for the given key.
  325. If ``forward`` is true, generate a forward compatibility test case:
  326. create a key and validate that it has the expected representation.
  327. Otherwise generate a backward compatibility test case: inject the
  328. key representation into storage and validate that it can be read
  329. correctly.
  330. """
  331. verb = 'save' if self.forward else 'read'
  332. tc = test_case.TestCase()
  333. tc.set_description('PSA storage {}: {}'.format(verb, key.description))
  334. dependencies = automatic_dependencies(
  335. key.lifetime.string, key.type.string,
  336. key.expected_usage, key.alg.string, key.alg2.string,
  337. )
  338. dependencies = finish_family_dependencies(dependencies, key.bits)
  339. tc.set_dependencies(dependencies)
  340. tc.set_function('key_storage_' + verb)
  341. if self.forward:
  342. extra_arguments = []
  343. else:
  344. flags = []
  345. # Some test keys have the RAW_DATA type and attributes that don't
  346. # necessarily make sense. We do this to validate numerical
  347. # encodings of the attributes.
  348. # Raw data keys have no useful exercise anyway so there is no
  349. # loss of test coverage.
  350. if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
  351. flags.append('TEST_FLAG_EXERCISE')
  352. if 'READ_ONLY' in key.lifetime.string:
  353. flags.append('TEST_FLAG_READ_ONLY')
  354. extra_arguments = [' | '.join(flags) if flags else '0']
  355. tc.set_arguments([key.lifetime.string,
  356. key.type.string, str(key.bits),
  357. key.expected_usage, key.alg.string, key.alg2.string,
  358. '"' + key.material.hex() + '"',
  359. '"' + key.hex() + '"',
  360. *extra_arguments])
  361. return tc
  362. def key_for_lifetime(
  363. self,
  364. lifetime: str,
  365. ) -> StorageTestData:
  366. """Construct a test key for the given lifetime."""
  367. short = lifetime
  368. short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
  369. r'', short)
  370. short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
  371. description = 'lifetime: ' + short
  372. key = StorageTestData(version=self.version,
  373. id=1, lifetime=lifetime,
  374. type='PSA_KEY_TYPE_RAW_DATA', bits=8,
  375. usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
  376. material=b'L',
  377. description=description)
  378. return key
  379. def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
  380. """Generate test keys covering lifetimes."""
  381. lifetimes = sorted(self.constructors.lifetimes)
  382. expressions = self.constructors.generate_expressions(lifetimes)
  383. for lifetime in expressions:
  384. # Don't attempt to create or load a volatile key in storage
  385. if 'VOLATILE' in lifetime:
  386. continue
  387. # Don't attempt to create a read-only key in storage,
  388. # but do attempt to load one.
  389. if 'READ_ONLY' in lifetime and self.forward:
  390. continue
  391. yield self.key_for_lifetime(lifetime)
  392. def keys_for_usage_flags(
  393. self,
  394. usage_flags: List[str],
  395. short: Optional[str] = None,
  396. test_implicit_usage: Optional[bool] = False
  397. ) -> Iterator[StorageTestData]:
  398. """Construct a test key for the given key usage."""
  399. usage = ' | '.join(usage_flags) if usage_flags else '0'
  400. if short is None:
  401. short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
  402. extra_desc = ' with implication' if test_implicit_usage else ''
  403. description = 'usage' + extra_desc + ': ' + short
  404. key1 = StorageTestData(version=self.version,
  405. id=1, lifetime=0x00000001,
  406. type='PSA_KEY_TYPE_RAW_DATA', bits=8,
  407. expected_usage=usage,
  408. usage=usage, alg=0, alg2=0,
  409. material=b'K',
  410. description=description)
  411. yield key1
  412. if test_implicit_usage:
  413. description = 'usage without implication' + ': ' + short
  414. key2 = StorageTestData(version=self.version,
  415. id=1, lifetime=0x00000001,
  416. type='PSA_KEY_TYPE_RAW_DATA', bits=8,
  417. without_implicit_usage=True,
  418. usage=usage, alg=0, alg2=0,
  419. material=b'K',
  420. description=description)
  421. yield key2
  422. def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
  423. """Generate test keys covering usage flags."""
  424. known_flags = sorted(self.constructors.key_usage_flags)
  425. yield from self.keys_for_usage_flags(['0'], **kwargs)
  426. for usage_flag in known_flags:
  427. yield from self.keys_for_usage_flags([usage_flag], **kwargs)
  428. for flag1, flag2 in zip(known_flags,
  429. known_flags[1:] + [known_flags[0]]):
  430. yield from self.keys_for_usage_flags([flag1, flag2], **kwargs)
  431. def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
  432. known_flags = sorted(self.constructors.key_usage_flags)
  433. yield from self.keys_for_usage_flags(known_flags, short='all known')
  434. def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
  435. yield from self.generate_keys_for_usage_flags()
  436. yield from self.generate_key_for_all_usage_flags()
  437. def keys_for_type(
  438. self,
  439. key_type: str,
  440. params: Optional[Iterable[str]] = None
  441. ) -> Iterator[StorageTestData]:
  442. """Generate test keys for the given key type.
  443. For key types that depend on a parameter (e.g. elliptic curve family),
  444. `param` is the parameter to pass to the constructor. Only a single
  445. parameter is supported.
  446. """
  447. kt = crypto_knowledge.KeyType(key_type, params)
  448. for bits in kt.sizes_to_test():
  449. usage_flags = 'PSA_KEY_USAGE_EXPORT'
  450. alg = 0
  451. alg2 = 0
  452. key_material = kt.key_material(bits)
  453. short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
  454. r'',
  455. kt.expression)
  456. description = 'type: {} {}-bit'.format(short_expression, bits)
  457. key = StorageTestData(version=self.version,
  458. id=1, lifetime=0x00000001,
  459. type=kt.expression, bits=bits,
  460. usage=usage_flags, alg=alg, alg2=alg2,
  461. material=key_material,
  462. description=description)
  463. yield key
  464. def all_keys_for_types(self) -> Iterator[StorageTestData]:
  465. """Generate test keys covering key types and their representations."""
  466. key_types = sorted(self.constructors.key_types)
  467. for key_type in self.constructors.generate_expressions(key_types):
  468. yield from self.keys_for_type(key_type)
  469. def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
  470. """Generate test keys for the specified algorithm."""
  471. # For now, we don't have information on the compatibility of key
  472. # types and algorithms. So we just test the encoding of algorithms,
  473. # and not that operations can be performed with them.
  474. descr = re.sub(r'PSA_ALG_', r'', alg)
  475. descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
  476. usage = 'PSA_KEY_USAGE_EXPORT'
  477. key1 = StorageTestData(version=self.version,
  478. id=1, lifetime=0x00000001,
  479. type='PSA_KEY_TYPE_RAW_DATA', bits=8,
  480. usage=usage, alg=alg, alg2=0,
  481. material=b'K',
  482. description='alg: ' + descr)
  483. yield key1
  484. key2 = StorageTestData(version=self.version,
  485. id=1, lifetime=0x00000001,
  486. type='PSA_KEY_TYPE_RAW_DATA', bits=8,
  487. usage=usage, alg=0, alg2=alg,
  488. material=b'L',
  489. description='alg2: ' + descr)
  490. yield key2
  491. def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
  492. """Generate test keys covering algorithm encodings."""
  493. algorithms = sorted(self.constructors.algorithms)
  494. for alg in self.constructors.generate_expressions(algorithms):
  495. yield from self.keys_for_algorithm(alg)
  496. def generate_all_keys(self) -> Iterator[StorageTestData]:
  497. """Generate all keys for the test cases."""
  498. yield from self.all_keys_for_lifetimes()
  499. yield from self.all_keys_for_usage_flags()
  500. yield from self.all_keys_for_types()
  501. yield from self.all_keys_for_algorithms()
  502. def all_test_cases(self) -> Iterator[test_case.TestCase]:
  503. """Generate all storage format test cases."""
  504. # First build a list of all keys, then construct all the corresponding
  505. # test cases. This allows all required information to be obtained in
  506. # one go, which is a significant performance gain as the information
  507. # includes numerical values obtained by compiling a C program.
  508. all_keys = list(self.generate_all_keys())
  509. for key in all_keys:
  510. if key.location_value() != 0:
  511. # Skip keys with a non-default location, because they
  512. # require a driver and we currently have no mechanism to
  513. # determine whether a driver is available.
  514. continue
  515. yield self.make_test_case(key)
  516. class StorageFormatForward(StorageFormat):
  517. """Storage format stability test cases for forward compatibility."""
  518. def __init__(self, info: Information, version: int) -> None:
  519. super().__init__(info, version, True)
  520. class StorageFormatV0(StorageFormat):
  521. """Storage format stability test cases for version 0 compatibility."""
  522. def __init__(self, info: Information) -> None:
  523. super().__init__(info, 0, False)
  524. def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
  525. """Generate test keys covering usage flags."""
  526. yield from self.generate_keys_for_usage_flags(test_implicit_usage=True)
  527. yield from self.generate_key_for_all_usage_flags()
  528. def keys_for_implicit_usage(
  529. self,
  530. implyer_usage: str,
  531. alg: str,
  532. key_type: crypto_knowledge.KeyType
  533. ) -> StorageTestData:
  534. # pylint: disable=too-many-locals
  535. """Generate test keys for the specified implicit usage flag,
  536. algorithm and key type combination.
  537. """
  538. bits = key_type.sizes_to_test()[0]
  539. implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
  540. usage_flags = 'PSA_KEY_USAGE_EXPORT'
  541. material_usage_flags = usage_flags + ' | ' + implyer_usage
  542. expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
  543. alg2 = 0
  544. key_material = key_type.key_material(bits)
  545. usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
  546. alg_expression = re.sub(r'PSA_ALG_', r'', alg)
  547. alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
  548. key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
  549. r'',
  550. key_type.expression)
  551. description = 'implied by {}: {} {} {}-bit'.format(
  552. usage_expression, alg_expression, key_type_expression, bits)
  553. key = StorageTestData(version=self.version,
  554. id=1, lifetime=0x00000001,
  555. type=key_type.expression, bits=bits,
  556. usage=material_usage_flags,
  557. expected_usage=expected_usage_flags,
  558. without_implicit_usage=True,
  559. alg=alg, alg2=alg2,
  560. material=key_material,
  561. description=description)
  562. return key
  563. def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
  564. # pylint: disable=too-many-locals
  565. """Match possible key types for sign algorithms."""
  566. # To create a valid combinaton both the algorithms and key types
  567. # must be filtered. Pair them with keywords created from its names.
  568. incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
  569. incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
  570. keyword_translation = {
  571. 'ECDSA': 'ECC',
  572. 'ED[0-9]*.*' : 'EDWARDS'
  573. }
  574. exclusive_keywords = {
  575. 'EDWARDS': 'ECC'
  576. }
  577. key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
  578. algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
  579. alg_with_keys = {} #type: Dict[str, List[str]]
  580. translation_table = str.maketrans('(', '_', ')')
  581. for alg in algorithms:
  582. # Generate keywords from the name of the algorithm
  583. alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
  584. # Translate keywords for better matching with the key types
  585. for keyword in alg_keywords.copy():
  586. for pattern, replace in keyword_translation.items():
  587. if re.match(pattern, keyword):
  588. alg_keywords.remove(keyword)
  589. alg_keywords.add(replace)
  590. # Filter out incompatible algortihms
  591. if not alg_keywords.isdisjoint(incompatible_alg_keyword):
  592. continue
  593. for key_type in key_types:
  594. # Generate keywords from the of the key type
  595. key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
  596. # Remove ambigious keywords
  597. for keyword1, keyword2 in exclusive_keywords.items():
  598. if keyword1 in key_type_keywords:
  599. key_type_keywords.remove(keyword2)
  600. if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
  601. not key_type_keywords.isdisjoint(alg_keywords):
  602. if alg in alg_with_keys:
  603. alg_with_keys[alg].append(key_type)
  604. else:
  605. alg_with_keys[alg] = [key_type]
  606. return alg_with_keys
  607. def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
  608. """Generate test keys for usage flag extensions."""
  609. # Generate a key type and algorithm pair for each extendable usage
  610. # flag to generate a valid key for exercising. The key is generated
  611. # without usage extension to check the extension compatiblity.
  612. alg_with_keys = self.gather_key_types_for_sign_alg()
  613. for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
  614. for alg in sorted(alg_with_keys):
  615. for key_type in sorted(alg_with_keys[alg]):
  616. # The key types must be filtered to fit the specific usage flag.
  617. kt = crypto_knowledge.KeyType(key_type)
  618. if kt.is_valid_for_signature(usage):
  619. yield self.keys_for_implicit_usage(usage, alg, kt)
  620. def generate_all_keys(self) -> Iterator[StorageTestData]:
  621. yield from super().generate_all_keys()
  622. yield from self.all_keys_for_implicit_usage()
  623. class TestGenerator:
  624. """Generate test data."""
  625. def __init__(self, options) -> None:
  626. self.test_suite_directory = self.get_option(options, 'directory',
  627. 'tests/suites')
  628. self.info = Information()
  629. @staticmethod
  630. def get_option(options, name: str, default: T) -> T:
  631. value = getattr(options, name, None)
  632. return default if value is None else value
  633. def filename_for(self, basename: str) -> str:
  634. """The location of the data file with the specified base name."""
  635. return os.path.join(self.test_suite_directory, basename + '.data')
  636. def write_test_data_file(self, basename: str,
  637. test_cases: Iterable[test_case.TestCase]) -> None:
  638. """Write the test cases to a .data file.
  639. The output file is ``basename + '.data'`` in the test suite directory.
  640. """
  641. filename = self.filename_for(basename)
  642. test_case.write_data_file(filename, test_cases)
  643. TARGETS = {
  644. 'test_suite_psa_crypto_generate_key.generated':
  645. lambda info: KeyGenerate(info).test_cases_for_key_generation(),
  646. 'test_suite_psa_crypto_not_supported.generated':
  647. lambda info: NotSupported(info).test_cases_for_not_supported(),
  648. 'test_suite_psa_crypto_storage_format.current':
  649. lambda info: StorageFormatForward(info, 0).all_test_cases(),
  650. 'test_suite_psa_crypto_storage_format.v0':
  651. lambda info: StorageFormatV0(info).all_test_cases(),
  652. } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
  653. def generate_target(self, name: str) -> None:
  654. test_cases = self.TARGETS[name](self.info)
  655. self.write_test_data_file(name, test_cases)
  656. def main(args):
  657. """Command line entry point."""
  658. parser = argparse.ArgumentParser(description=__doc__)
  659. parser.add_argument('--list', action='store_true',
  660. help='List available targets and exit')
  661. parser.add_argument('targets', nargs='*', metavar='TARGET',
  662. help='Target file to generate (default: all; "-": none)')
  663. options = parser.parse_args(args)
  664. generator = TestGenerator(options)
  665. if options.list:
  666. for name in sorted(generator.TARGETS):
  667. print(generator.filename_for(name))
  668. return
  669. if options.targets:
  670. # Allow "-" as a special case so you can run
  671. # ``generate_psa_tests.py - $targets`` and it works uniformly whether
  672. # ``$targets`` is empty or not.
  673. options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
  674. for target in options.targets
  675. if target != '-']
  676. else:
  677. options.targets = sorted(generator.TARGETS)
  678. for target in options.targets:
  679. generator.generate_target(target)
  680. if __name__ == '__main__':
  681. main(sys.argv[1:])