New test suite for the low-level hash interface

Some basic test coverage for now:

* Nominal operation.
* Larger output buffer.
* Clone an operation and use it after the original operation stops.

Generate test data automatically. For the time being, only do that for
hashes that Python supports natively. Supporting all algorithms is future
work.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
This commit is contained in:
Gilles Peskine 2023-06-20 15:22:53 +02:00
parent fdb722384b
commit c9187c5866
5 changed files with 353 additions and 0 deletions

View File

@ -0,0 +1,123 @@
"""Generate test data for cryptographic mechanisms.
This module is a work in progress, only implementing a few cases for now.
"""
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import hashlib
from typing import Callable, Dict, Iterator, List, Optional #pylint: disable=unused-import
from . import crypto_knowledge
from . import psa_information
from . import test_case
def psa_low_level_dependencies(*expressions: str) -> List[str]:
"""Infer dependencies of a PSA low-level test case by looking for PSA_xxx symbols.
This function generates MBEDTLS_PSA_BUILTIN_xxx symbols.
"""
high_level = psa_information.automatic_dependencies(*expressions)
for dep in high_level:
assert dep.startswith('PSA_WANT_')
return ['MBEDTLS_PSA_BUILTIN_' + dep[9:] for dep in high_level]
class HashPSALowLevel:
"""Generate test cases for the PSA low-level hash interface."""
def __init__(self, info: psa_information.Information) -> None:
self.info = info
base_algorithms = sorted(info.constructors.algorithms)
all_algorithms = \
[crypto_knowledge.Algorithm(expr)
for expr in info.constructors.generate_expressions(base_algorithms)]
self.algorithms = \
[alg
for alg in all_algorithms
if (not alg.is_wildcard and
alg.can_do(crypto_knowledge.AlgorithmCategory.HASH))]
# CALCULATE[alg] = function to return the hash of its argument in hex
# TO-DO: implement the None entries with a third-party library, because
# hashlib might not have everything, depending on the Python version and
# the underlying OpenSSL. On Ubuntu 16.04, truncated sha512 and sha3/shake
# are not available. On Ubuntu 22.04, md2, md4 and ripemd160 are not
# available.
CALCULATE = {
'PSA_ALG_MD5': lambda data: hashlib.md5(data).hexdigest(),
'PSA_ALG_RIPEMD160': None, #lambda data: hashlib.new('ripdemd160').hexdigest()
'PSA_ALG_SHA_1': lambda data: hashlib.sha1(data).hexdigest(),
'PSA_ALG_SHA_224': lambda data: hashlib.sha224(data).hexdigest(),
'PSA_ALG_SHA_256': lambda data: hashlib.sha256(data).hexdigest(),
'PSA_ALG_SHA_384': lambda data: hashlib.sha384(data).hexdigest(),
'PSA_ALG_SHA_512': lambda data: hashlib.sha512(data).hexdigest(),
'PSA_ALG_SHA_512_224': None, #lambda data: hashlib.new('sha512_224').hexdigest()
'PSA_ALG_SHA_512_256': None, #lambda data: hashlib.new('sha512_256').hexdigest()
'PSA_ALG_SHA3_224': None, #lambda data: hashlib.sha3_224(data).hexdigest(),
'PSA_ALG_SHA3_256': None, #lambda data: hashlib.sha3_256(data).hexdigest(),
'PSA_ALG_SHA3_384': None, #lambda data: hashlib.sha3_384(data).hexdigest(),
'PSA_ALG_SHA3_512': None, #lambda data: hashlib.sha3_512(data).hexdigest(),
'PSA_ALG_SHAKE256_512': None, #lambda data: hashlib.shake_256(data).hexdigest(64),
} #typing: Optional[Dict[str, Callable[[bytes], str]]]
@staticmethod
def one_test_case(alg: crypto_knowledge.Algorithm,
function: str, note: str,
arguments: List[str]) -> test_case.TestCase:
"""Construct one test case involving a hash."""
tc = test_case.TestCase()
tc.set_description('{}{} {}'
.format(function,
' ' + note if note else '',
alg.short_expression()))
tc.set_dependencies(psa_low_level_dependencies(alg.expression))
tc.set_function(function)
tc.set_arguments([alg.expression] +
['"{}"'.format(arg) for arg in arguments])
return tc
def test_cases_for_hash(self,
alg: crypto_knowledge.Algorithm
) -> Iterator[test_case.TestCase]:
"""Enumerate all test cases for one hash algorithm."""
calc = self.CALCULATE[alg.expression]
if calc is None:
return # not implemented yet
short = b'abc'
hash_short = calc(short)
long = (b'Hello, world. Here are 16 unprintable bytes: ['
b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a'
b'\x80\x81\x82\x83\xfe\xff]. '
b' This message was brought to you by a natural intelligence. '
b' If you can read this, good luck with your debugging!')
hash_long = calc(long)
yield self.one_test_case(alg, 'hash_empty', '', [calc(b'')])
yield self.one_test_case(alg, 'hash_valid_one_shot', '',
[short.hex(), hash_short])
for n in [0, 1, 64, len(long) - 1, len(long)]:
yield self.one_test_case(alg, 'hash_valid_multipart',
'{} + {}'.format(n, len(long) - n),
[long[:n].hex(), calc(long[:n]),
long[n:].hex(), hash_long])
def all_test_cases(self) -> Iterator[test_case.TestCase]:
"""Enumerate all test cases for all hash algorithms."""
for alg in self.algorithms:
yield from self.test_cases_for_hash(alg)

View File

@ -118,6 +118,7 @@ if(GEN_FILES)
--directory ${CMAKE_CURRENT_BINARY_DIR}/suites
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/../tests/scripts/generate_psa_tests.py
${CMAKE_CURRENT_SOURCE_DIR}/../scripts/mbedtls_dev/crypto_data_tests.py
${CMAKE_CURRENT_SOURCE_DIR}/../scripts/mbedtls_dev/crypto_knowledge.py
${CMAKE_CURRENT_SOURCE_DIR}/../scripts/mbedtls_dev/macro_collector.py
${CMAKE_CURRENT_SOURCE_DIR}/../scripts/mbedtls_dev/psa_information.py

View File

@ -121,6 +121,7 @@ generated_ecp_test_data:
$(GENERATED_PSA_DATA_FILES): generated_psa_test_data
generated_psa_test_data: scripts/generate_psa_tests.py
generated_psa_test_data: ../scripts/mbedtls_dev/crypto_data_tests.py
generated_psa_test_data: ../scripts/mbedtls_dev/crypto_knowledge.py
generated_psa_test_data: ../scripts/mbedtls_dev/macro_collector.py
generated_psa_test_data: ../scripts/mbedtls_dev/psa_information.py

View File

@ -26,6 +26,7 @@ import sys
from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional
import scripts_path # pylint: disable=unused-import
from mbedtls_dev import crypto_data_tests
from mbedtls_dev import crypto_knowledge
from mbedtls_dev import macro_collector #pylint: disable=unused-import
from mbedtls_dev import psa_information
@ -839,6 +840,8 @@ class PSATestGenerator(test_data_generation.TestGenerator):
lambda info: KeyGenerate(info).test_cases_for_key_generation(),
'test_suite_psa_crypto_not_supported.generated':
lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
'test_suite_psa_crypto_low_hash.generated':
lambda info: crypto_data_tests.HashPSALowLevel(info).all_test_cases(),
'test_suite_psa_crypto_op_fail.generated':
lambda info: OpFail(info).all_test_cases(),
'test_suite_psa_crypto_storage_format.current':

View File

@ -0,0 +1,225 @@
/* BEGIN_HEADER */
/*
* Test suite for the PSA hash built-in driver
*
* This test suite exercises some aspects of the built-in PSA driver for
* hash algorithms (psa_crypto_hash.c). This code is mostly tested via
* the application interface (above the PSA API layer) and via tests of
* individual hash modules. The goal of this test suite is to ensure that
* the driver dispatch layer behaves correctly even when not invoked via
* the API layer, but directly from another driver.
*
* This test suite is currently incomplete. It focuses on non-regression
* tests for past bugs or near misses.
*/
#include <psa_crypto_hash.h>
/* END_HEADER */
/* BEGIN_DEPENDENCIES
* depends_on:MBEDTLS_PSA_BUILTIN_HASH
* END_DEPENDENCIES
*/
/* BEGIN_CASE */
void hash_valid_one_shot(int alg_arg, data_t *input,
data_t *expected)
{
psa_algorithm_t alg = alg_arg;
uint8_t *output = NULL;
size_t output_size = expected->len;
size_t length = SIZE_MAX;
/* Nominal case */
ASSERT_ALLOC(output, output_size);
TEST_EQUAL(mbedtls_psa_hash_compute(alg, input->x, input->len,
output, output_size, &length),
PSA_SUCCESS);
ASSERT_COMPARE(expected->x, expected->len, output, length);
mbedtls_free(output);
output = NULL;
/* Larger output buffer */
output_size = expected->len + 1;
ASSERT_ALLOC(output, output_size);
TEST_EQUAL(mbedtls_psa_hash_compute(alg, input->x, input->len,
output, output_size, &length),
PSA_SUCCESS);
ASSERT_COMPARE(expected->x, expected->len, output, length);
mbedtls_free(output);
output = NULL;
#if 0
/* Smaller output buffer (does not have to work!) */
output_size = expected->len - 1;
ASSERT_ALLOC(output, output_size);
TEST_EQUAL(mbedtls_psa_hash_compute(alg, input->x, input->len,
output, output_size, &length),
PSA_ERROR_BUFFER_TOO_SMALL);
mbedtls_free(output);
output = NULL;
#endif
exit:
mbedtls_free(output);
}
/* END_CASE */
/* BEGIN_CASE */
void hash_valid_multipart(int alg_arg,
data_t *input1, data_t *expected1,
data_t *input2, data_t *expected2)
{
psa_algorithm_t alg = alg_arg;
uint8_t *output = NULL;
size_t output_size = expected1->len;
size_t length = SIZE_MAX;
mbedtls_psa_hash_operation_t operation0; // original
memset(&operation0, 0, sizeof(operation0));
mbedtls_psa_hash_operation_t clone_start; // cloned after setup
memset(&clone_start, 0, sizeof(clone_start));
mbedtls_psa_hash_operation_t clone_middle; // cloned between updates
memset(&clone_middle, 0, sizeof(clone_middle));
mbedtls_psa_hash_operation_t clone_end; // cloned before finish
memset(&clone_end, 0, sizeof(clone_end));
mbedtls_psa_hash_operation_t clone_more; // cloned before finish
memset(&clone_more, 0, sizeof(clone_more));
/* Nominal case with two update calls */
ASSERT_ALLOC(output, output_size);
TEST_EQUAL(mbedtls_psa_hash_setup(&operation0, alg),
PSA_SUCCESS);
TEST_EQUAL(mbedtls_psa_hash_clone(&operation0, &clone_start),
PSA_SUCCESS);
TEST_EQUAL(mbedtls_psa_hash_update(&operation0, input1->x, input1->len),
PSA_SUCCESS);
TEST_EQUAL(mbedtls_psa_hash_clone(&operation0, &clone_middle),
PSA_SUCCESS);
TEST_EQUAL(mbedtls_psa_hash_update(&operation0, input2->x, input2->len),
PSA_SUCCESS);
TEST_EQUAL(mbedtls_psa_hash_clone(&operation0, &clone_end),
PSA_SUCCESS);
TEST_EQUAL(mbedtls_psa_hash_finish(&operation0,
output, output_size, &length),
PSA_SUCCESS);
ASSERT_COMPARE(expected2->x, expected2->len, output, length);
/* Nominal case with an operation cloned after setup */
memset(output, 0, output_size);
TEST_EQUAL(mbedtls_psa_hash_update(&clone_start, input1->x, input1->len),
PSA_SUCCESS);
TEST_EQUAL(mbedtls_psa_hash_finish(&clone_start,
output, output_size, &length),
PSA_SUCCESS);
ASSERT_COMPARE(expected1->x, expected1->len, output, length);
/* Nominal case with an operation cloned between updates */
memset(output, 0, output_size);
TEST_EQUAL(mbedtls_psa_hash_update(&clone_middle, input2->x, input2->len),
PSA_SUCCESS);
TEST_EQUAL(mbedtls_psa_hash_finish(&clone_middle,
output, output_size, &length),
PSA_SUCCESS);
ASSERT_COMPARE(expected2->x, expected2->len, output, length);
/* Nominal case with an operation cloned before finish */
TEST_EQUAL(mbedtls_psa_hash_clone(&clone_end, &clone_more),
PSA_SUCCESS);
memset(output, 0, output_size);
TEST_EQUAL(mbedtls_psa_hash_finish(&clone_end,
output, output_size, &length),
PSA_SUCCESS);
ASSERT_COMPARE(expected2->x, expected2->len, output, length);
mbedtls_free(output);
output = NULL;
/* Larger output buffer */
TEST_EQUAL(mbedtls_psa_hash_clone(&clone_more, &clone_end),
PSA_SUCCESS);
output_size = expected2->len + 1;
ASSERT_ALLOC(output, output_size);
TEST_EQUAL(mbedtls_psa_hash_finish(&clone_end,
output, output_size, &length),
PSA_SUCCESS);
ASSERT_COMPARE(expected2->x, expected2->len, output, length);
mbedtls_free(output);
output = NULL;
#if 0
/* Smaller output buffer (does not have to work!) */
TEST_EQUAL(mbedtls_psa_hash_clone(&clone_more, &clone_end),
PSA_SUCCESS);
output_size = expected2->len - 1;
ASSERT_ALLOC(output, output_size);
TEST_EQUAL(mbedtls_psa_hash_finish(&clone_end,
output, output_size, &length),
PSA_ERROR_BUFFER_TOO_SMALL);
mbedtls_free(output);
output = NULL;
#endif
/* Nominal case again after an error in a cloned operation */
output_size = expected2->len;
ASSERT_ALLOC(output, output_size);
TEST_EQUAL(mbedtls_psa_hash_finish(&clone_more,
output, output_size, &length),
PSA_SUCCESS);
ASSERT_COMPARE(expected2->x, expected2->len, output, length);
mbedtls_free(output);
output = NULL;
exit:
mbedtls_free(output);
mbedtls_psa_hash_abort(&operation0);
mbedtls_psa_hash_abort(&clone_start);
mbedtls_psa_hash_abort(&clone_middle);
mbedtls_psa_hash_abort(&clone_end);
mbedtls_psa_hash_abort(&clone_more);
}
/* END_CASE */
/* BEGIN_CASE */
void hash_empty(int alg_arg, data_t *expected)
{
psa_algorithm_t alg = alg_arg;
uint8_t *output = NULL;
size_t output_size = expected->len;
size_t length = SIZE_MAX;
mbedtls_psa_hash_operation_t operation;
memset(&operation, 0, sizeof(operation));
ASSERT_ALLOC(output, output_size);
/* One-shot */
TEST_EQUAL(mbedtls_psa_hash_compute(alg, NULL, 0,
output, output_size, &length),
PSA_SUCCESS);
ASSERT_COMPARE(expected->x, expected->len, output, length);
/* Multipart, no update */
memset(output, 0, output_size);
TEST_EQUAL(mbedtls_psa_hash_setup(&operation, alg),
PSA_SUCCESS);
TEST_EQUAL(mbedtls_psa_hash_finish(&operation,
output, output_size, &length),
PSA_SUCCESS);
ASSERT_COMPARE(expected->x, expected->len, output, length);
/* Multipart, one update */
memset(output, 0, output_size);
memset(&operation, 0, sizeof(operation));
TEST_EQUAL(mbedtls_psa_hash_setup(&operation, alg),
PSA_SUCCESS);
TEST_EQUAL(mbedtls_psa_hash_update(&operation, NULL, 0),
PSA_SUCCESS);
TEST_EQUAL(mbedtls_psa_hash_finish(&operation,
output, output_size, &length),
PSA_SUCCESS);
ASSERT_COMPARE(expected->x, expected->len, output, length);
exit:
mbedtls_free(output);
mbedtls_psa_hash_abort(&operation);
}
/* END_CASE */