Reference implementations of PQC
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

171 regels
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)