選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

171 行
5.7 KiB

  1. """
  2. Verify the metadata specified in the META.yml files.
  3. """
  4. import copy
  5. import itertools
  6. import pytest
  7. import helpers
  8. import pqclean
  9. @pytest.mark.parametrize(
  10. 'scheme',
  11. pqclean.Scheme.all_schemes(),
  12. ids=str,
  13. )
  14. @helpers.filtered_test
  15. def test_metadata(scheme):
  16. metadata = scheme.metadata()
  17. specification = EXPECTED_FIELDS.items()
  18. if scheme.type == 'kem':
  19. specification = itertools.chain(specification, KEM_FIELDS.items())
  20. elif scheme.type == 'sign':
  21. specification = itertools.chain(specification,
  22. SIGNATURE_FIELDS.items())
  23. else:
  24. assert False, "Wrong type of metadata"
  25. check_spec(copy.deepcopy(metadata), specification)
  26. implementation_names_in_yaml = set(
  27. i['name'] for i in metadata['implementations'])
  28. if len(implementation_names_in_yaml) != len(metadata['implementations']):
  29. raise AssertionError("Implementations in YAML file are not distinct")
  30. implementations_on_disk = set(i.name for i in scheme.implementations)
  31. if implementation_names_in_yaml != implementations_on_disk:
  32. raise AssertionError("Implementations in YAML file {} and "
  33. "implementations on disk {} do not match"
  34. .format(implementation_names_in_yaml,
  35. implementations_on_disk))
  36. EXPECTED_FIELDS = {
  37. 'name': {'type': str},
  38. 'type': {'type': str},
  39. 'claimed-nist-level': {'type': int, 'min': 1, 'max': 5},
  40. 'length-public-key': {'type': int, 'min': 1},
  41. 'length-secret-key': {'type': int, 'min': 1},
  42. 'nistkat-sha256': {'type': str, 'length': 64},
  43. 'principal-submitters': {'type': list, 'elements': {'type': str}},
  44. 'auxiliary-submitters': {
  45. 'type': list, 'elements': {'type': str}, 'optional': True},
  46. 'implementations': {
  47. 'type': list,
  48. 'elements': {
  49. 'type': dict,
  50. 'spec': {
  51. 'name': {'type': str},
  52. 'version': {'type': str},
  53. 'supported_platforms': {
  54. 'type': list,
  55. 'optional': True,
  56. 'elements': {
  57. 'type': dict,
  58. 'spec': {
  59. 'architecture': {
  60. 'type': str,
  61. 'values': ['x86', 'x86_64', 'aarch64']},
  62. 'required_flags': {
  63. 'type': list,
  64. 'optional': True,
  65. 'elements': {'type': str},
  66. },
  67. 'operating_systems': {
  68. 'type': list,
  69. 'optional': True,
  70. 'elements': {
  71. 'type': str,
  72. 'values': ['Linux', 'Windows', 'Darwin'],
  73. },
  74. },
  75. },
  76. },
  77. },
  78. },
  79. },
  80. },
  81. }
  82. KEM_FIELDS = {
  83. 'claimed-security': {'type': str, 'values': ['IND-CPA', 'IND-CCA2']},
  84. 'length-ciphertext': {'type': int, 'min': 1},
  85. 'length-shared-secret': {'type': int, 'min': 1},
  86. }
  87. SIGNATURE_FIELDS = {
  88. 'length-signature': {'type': int, 'min': 1},
  89. 'testvectors-sha256': {'type': str, 'length': 64},
  90. }
  91. def check_spec(metadata, spec):
  92. for field, props in spec:
  93. if field not in metadata and 'optional' not in props:
  94. raise AssertionError("Field '{}' not present.".format(field))
  95. # validate element
  96. if field in metadata:
  97. check_element(field, metadata[field], props)
  98. # delete it to detect extras
  99. del metadata[field]
  100. # Done checking all specified fields, check if we have extras
  101. for field, value in metadata.items():
  102. raise AssertionError(
  103. "Unexpected item '{}' with value '{}'".format(field, value))
  104. def check_element(field, element, props):
  105. type_ = props['type']
  106. # Validate type of element
  107. type_(element)
  108. # Strs are valid lists otherwise
  109. if type_ == list and type(element) != list:
  110. raise ValueError("Field {} not a list".format(field))
  111. # lists are valid dicts otherwise
  112. if type_ == dict and type(element) != dict:
  113. raise ValueError("Field {} not a dict".format(field))
  114. if type_ == int:
  115. element = int(element)
  116. if 'min' in props:
  117. if element < props['min']:
  118. raise ValueError("Value of field '{}' is lower than minimum "
  119. "value {}".format(field, props['min']))
  120. if 'max' in props:
  121. if element > props['max']:
  122. raise ValueError("Value of field '{}' is larger than maximum"
  123. " value {}"
  124. .format(field, props['max']))
  125. if type_ == str:
  126. if 'length' in props:
  127. actual_len = len(element)
  128. if actual_len != props['length']:
  129. raise ValueError("Value of field '{}' should be length {}"
  130. " but was length {}"
  131. .format(field, props['length'], actual_len))
  132. if 'values' in props and element not in props['values']:
  133. raise ValueError("'{}' should be in {}"
  134. .format(element, props['values']))
  135. if type_ == list: # recursively check the elements
  136. for el in element:
  137. check_element('element of {}'.format(field), el, props['elements'])
  138. if type_ == dict:
  139. check_spec(element, props['spec'].items())
  140. if __name__ == '__main__':
  141. import sys
  142. pytest.main(sys.argv)