Skip to content

Commit c6610dd

Browse files
Merge pull request #282 from gilles-peskine-arm/mldsa-pqcp-driver-framework
Generate MLDSA test cases for the driver and dispatch layers
2 parents b80f4d5 + 1fba034 commit c6610dd

6 files changed

Lines changed: 337 additions & 195 deletions

File tree

scripts/check-python-files.sh

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ elif [ "$1" = "--can-mypy" ]; then
5555
fi
5656

5757
echo 'Running pylint ...'
58+
# Exclude `maintainer` subdirectories, because they can contain code
59+
# that does not work with the versions of pylint and mypy we use on the CI.
60+
# https://github.com/Mbed-TLS/mbedtls-framework/issues/293
61+
#
5862
# When we move Python code between repositories, there is a transition
5963
# period during which code is duplicated between the old repository and
6064
# the new repository.
@@ -64,7 +68,9 @@ echo 'Running pylint ...'
6468
# runs of pylint: one for the A files, and one for the others.
6569
# Remove exceptions below once the A file (or the moved code in the A file)
6670
# has been removed from all consuming branches.
67-
find framework/scripts scripts tests/scripts -name '*.py' \( \
71+
find framework/scripts scripts tests/scripts \
72+
-name maintainer -prune -o \
73+
-name '*.py' \( \
6874
! -path scripts/abi_check.py \
6975
! -path scripts/code_size_compare.py \
7076
! -path scripts/ecp_comb_table.py \
@@ -87,7 +93,10 @@ $PYTHON -m mypy framework/scripts || {
8793
ret=1
8894
}
8995

90-
$PYTHON -m mypy scripts tests/scripts || {
96+
# Exclude `maintainer` subdirectories, because they can contain code
97+
# that does not work with the versions of pylint and mypy we use on the CI.
98+
# https://github.com/Mbed-TLS/mbedtls-framework/issues/293
99+
$PYTHON -m mypy --exclude maintainer scripts tests/scripts || {
91100
echo >&2 "mypy reported errors in the parent repository"
92101
ret=1
93102
}

scripts/mbedtls_framework/macro_collector.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,13 @@ class read_file_lines:
3737
except that if process(line) raises an exception, then the read_file_lines
3838
snippet annotates the exception with the file name and line number.
3939
"""
40-
def __init__(self, filename: str, binary: bool = False) -> None:
40+
def __init__(self, filename: str) -> None:
4141
self.filename = filename
4242
self.file = None #type: Optional[IO[str]]
4343
self.line_number = 'entry' #type: Union[int, str]
4444
self.generator = None #type: Optional[Iterable[Tuple[int, str]]]
45-
self.binary = binary
4645
def __enter__(self) -> 'read_file_lines':
47-
self.file = open(self.filename, 'rb' if self.binary else 'r')
46+
self.file = open(self.filename)
4847
self.generator = enumerate(self.file)
4948
return self
5049
def __iter__(self) -> Iterator[str]:
@@ -517,10 +516,10 @@ def parse_header_line(self, line: str) -> None:
517516
_nonascii_re = re.compile(rb'[^\x00-\x7f]+') #type: Pattern
518517
def parse_header(self, filename: str) -> None:
519518
"""Parse a C header file, looking for "#define PSA_xxx"."""
520-
with read_file_lines(filename, binary=True) as lines:
521-
for line in lines:
522-
line = re.sub(self._nonascii_re, rb'', line).decode('ascii')
523-
self.parse_header_line(line)
519+
with open(filename, 'rb') as input_:
520+
for line in input_:
521+
line = re.sub(self._nonascii_re, rb'', line)
522+
self.parse_header_line(line.decode('ascii'))
524523

525524
_macro_identifier_re = re.compile(r'[A-Z]\w+')
526525
def generate_undeclared_names(self, expr: str) -> Iterable[str]:

tests/src/drivers/test_driver_signature.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,30 @@
3939
#include LIBTESTDRIVER1_PSA_DRIVER_INTERNAL_HEADER(psa_crypto_rsa.h)
4040
#endif
4141

42+
/* This file is part of the framework and needs to be compatible with all
43+
* maintained branches of Mbed TLS and TF-PSA-Crypto.
44+
*
45+
* - Until shortly before TF-PSA-Crypto 1.1.0, ML-DSA does not exist at all.
46+
* - In TF-PSA-Crypto 1.1.0, TF_PSA_CRYPTO_PQCP_MLDSA_ENABLED exists, but
47+
* there is no driver dispatch for it yet, so this driver doesn't need to
48+
* worry about ML-DSA.
49+
* - Shortly after TF-PSA-Crypto 1.1.0, in
50+
* https://github.com/Mbed-TLS/TF-PSA-Crypto/pull/700, we introduced
51+
* driver dispatch for ML-DSA, but the macro PSA_ALG_IS_ML_DSA is not
52+
* in the API yet, only in a private header. Including this private header
53+
* is a pain due to how our various build scripts set up include paths, so
54+
* we don't do it. Instead, define PSA_ALG_IS_ML_DSA manually: it's the
55+
* only thing we need.
56+
* - Later we will add ML-DSA to the API, including the definition of
57+
* PSA_ALG_IS_ML_DSA. After that we may also add driver dispatch testing
58+
* for ML-DSA.
59+
*/
60+
#if !defined(PSA_ALG_IS_ML_DSA)
61+
/* Pure ML-DSA (hedged or deterministic) */
62+
#define PSA_ALG_IS_ML_DSA(alg) \
63+
((alg) == 0x06004400u || (alg) == 0x06004500u)
64+
#endif
65+
4266
#include <string.h>
4367

4468
mbedtls_test_driver_signature_hooks_t
@@ -213,6 +237,20 @@ psa_status_t mbedtls_test_transparent_signature_sign_message(
213237
return PSA_SUCCESS;
214238
}
215239

240+
#if defined(TF_PSA_CRYPTO_PQCP_MLDSA_ENABLED)
241+
/* Pure ML-DSA is not a sign-the-hash algorithm. At the moment, this
242+
* function only knows how to deal with sign-the-hash algorithms.
243+
* So give up and let the next driver in the chain handle the algorithm.
244+
* For pure ML-DSA, this will be the pqcp driver, which does not have
245+
* a libtestdriver1 variant, meaning that we can't test "driver-only"
246+
* builds for pure ML-DSA, but we can have ML-DSA enabled in builds that
247+
* dispatch through the test driver.
248+
*/
249+
if (PSA_ALG_IS_ML_DSA(alg)) {
250+
return PSA_ERROR_NOT_SUPPORTED;
251+
}
252+
#endif
253+
216254
#if defined(MBEDTLS_TEST_LIBTESTDRIVER1) && \
217255
defined(LIBTESTDRIVER1_MBEDTLS_PSA_BUILTIN_HASH)
218256
status = libtestdriver1_mbedtls_psa_hash_compute(
@@ -280,6 +318,20 @@ psa_status_t mbedtls_test_transparent_signature_verify_message(
280318
return mbedtls_test_driver_signature_verify_hooks.forced_status;
281319
}
282320

321+
#if defined(TF_PSA_CRYPTO_PQCP_MLDSA_ENABLED)
322+
/* Pure ML-DSA is not a sign-the-hash algorithm. At the moment, this
323+
* function only knows how to deal with sign-the-hash algorithms.
324+
* So give up and let the next driver in the chain handle the algorithm.
325+
* For pure ML-DSA, this will be the pqcp driver, which does not have
326+
* a libtestdriver1 variant, meaning that we can't test "driver-only"
327+
* builds for pure ML-DSA, but we can have ML-DSA enabled in builds that
328+
* dispatch through the test driver.
329+
*/
330+
if (PSA_ALG_IS_ML_DSA(alg)) {
331+
return PSA_ERROR_NOT_SUPPORTED;
332+
}
333+
#endif
334+
283335
#if defined(MBEDTLS_TEST_LIBTESTDRIVER1) && \
284336
defined(LIBTESTDRIVER1_MBEDTLS_PSA_BUILTIN_HASH)
285337
status = libtestdriver1_mbedtls_psa_hash_compute(

util/generate_mldsa_tests.py

Lines changed: 6 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -1,203 +1,23 @@
11
#!/usr/bin/env python3
22
"""Generate ML-DSA test cases.
3-
"""
43
5-
# Copyright The Mbed TLS Contributors
6-
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
4+
This is a transitional script that does not handle different feature sets
5+
in different states of TF-PSA-Crypto. The live version of this script
6+
is `scripts/maintainer/generate_mldsa_tests.py` in TF-PSA-Crypto.
7+
"""
78

89
import sys
9-
from typing import Iterable, List, Optional
10-
11-
# pip install dilithium-py
12-
import dilithium_py.ml_dsa #type: ignore
1310

1411
import scripts_path # pylint: disable=unused-import
15-
from mbedtls_framework import test_case
1612
from mbedtls_framework import test_data_generation
17-
18-
# ML_DSA instances for pure ML-DSA
19-
PURE = {
20-
#44: dilithium_py.ml_dsa.ML_DSA_44,
21-
#65: dilithium_py.ml_dsa.ML_DSA_65,
22-
87: dilithium_py.ml_dsa.ML_DSA_87,
23-
}
24-
25-
# ML_DSA instances for HashML-DSA
26-
HASH = {
27-
#44: dilithium_py.ml_dsa.HASH_ML_DSA_44_WITH_SHA512,
28-
#65: dilithium_py.ml_dsa.HASH_ML_DSA_65_WITH_SHA512,
29-
87: dilithium_py.ml_dsa.HASH_ML_DSA_87_WITH_SHA512,
30-
}
31-
32-
# Seeds (i.e. private keys) to test with.
33-
SEEDS = [
34-
b'There was once upon a time a ...',
35-
b'\x00' * 32,
36-
]
37-
38-
class Key:
39-
"""An MLDSA key pair."""
40-
#pylint: disable=too-few-public-methods
41-
42-
def __init__(self, kl: int, seed: bytes) -> None:
43-
self.kl = kl #pylint: disable=invalid-name
44-
self.seed = seed
45-
self.public, self.secret = PURE[kl]._keygen_internal(seed)
46-
47-
def sign_message(self, message: bytes, deterministic: bool) -> bytes:
48-
PURE[self.kl].set_drbg_seed(bytes(48))
49-
return PURE[self.kl].sign(self.secret, message,
50-
deterministic=deterministic)
51-
52-
# Key pairs to test with.
53-
KEYS = {kl: [Key(kl, seed) for seed in SEEDS]
54-
for kl in sorted(PURE.keys())}
55-
56-
# Input messages to test with.
57-
MESSAGES = [
58-
(b'This is a test', ''),
59-
(b'', 'empty message'),
60-
(b'\x00', '"\\x00"'),
61-
(b'\x01', '"\\x01"'),
62-
(b'ACBDEFGHIJ' * 100, '1000B'),
63-
]
64-
65-
66-
class API:
67-
"""Abstract base class for the interface of the test functions."""
68-
69-
@classmethod
70-
def function(cls, func: str, kl: int) -> str:
71-
raise NotImplementedError
72-
73-
@classmethod
74-
def metadata_arguments(cls,
75-
kl: int,
76-
pair: bool,
77-
deterministic: Optional[bool]) -> List[str]:
78-
raise NotImplementedError
79-
80-
@classmethod
81-
def final_arguments(cls) -> List[str]:
82-
return []
83-
84-
@classmethod
85-
def secret_is_seed(cls) -> bool:
86-
return True
87-
88-
89-
class PQCPAPI(API):
90-
"""Test mldsa-native entry points."""
91-
92-
@classmethod
93-
def function(cls, func: str, kl: int) -> str:
94-
return f'{func}_{kl}'
95-
96-
@classmethod
97-
def metadata_arguments(cls,
98-
_kl: int,
99-
_pair: bool,
100-
_deterministic: Optional[bool]) -> List[str]:
101-
return []
102-
103-
@classmethod
104-
def secret_is_seed(cls) -> bool:
105-
return False
106-
107-
108-
def one_mldsa_key_pair_from_seed(key: Key,
109-
descr: str) -> test_case.TestCase:
110-
"""Construct one test case for mldsa-native keypair_internal()."""
111-
tc = test_case.TestCase()
112-
tc.set_function(f'key_pair_from_seed_{key.kl}')
113-
tc.set_dependencies([f'TF_PSA_CRYPTO_PQCP_MLDSA_{key.kl}_ENABLED'])
114-
tc.set_arguments([
115-
test_case.hex_string(key.seed),
116-
test_case.hex_string(key.secret),
117-
test_case.hex_string(key.public),
118-
])
119-
tc.set_description(f'MLDSA-{key.kl} key pair from seed {descr}')
120-
return tc
121-
122-
def gen_pqcp_key_management(kl: int) -> Iterable[test_case.TestCase]:
123-
"""Generate test cases for mldsa-native keypair_internal()."""
124-
for i, key in enumerate(KEYS[kl], 1):
125-
yield one_mldsa_key_pair_from_seed(key, f'key#{i}')
126-
127-
def one_mldsa_sign_deterministic_pure(api: API,
128-
key: Key,
129-
message: bytes,
130-
descr: str) -> test_case.TestCase:
131-
"""Construct one test case for deterministic signature."""
132-
signature = key.sign_message(message, deterministic=True)
133-
tc = test_case.TestCase()
134-
tc.set_function(api.function('sign_deterministic_pure', key.kl))
135-
tc.set_dependencies([f'TF_PSA_CRYPTO_PQCP_MLDSA_{key.kl}_ENABLED'])
136-
tc.set_arguments(api.metadata_arguments(key.kl, True, True) + [
137-
test_case.hex_string(key.seed if api.secret_is_seed() else key.secret),
138-
test_case.hex_string(message),
139-
test_case.hex_string(signature),
140-
] + api.final_arguments())
141-
tc.set_description(f'MLDSA-{key.kl} sign deterministic {descr}')
142-
return tc
143-
144-
def one_mldsa_verify_pure(api: API,
145-
key: Key,
146-
message: bytes,
147-
deterministic: bool,
148-
descr: str) -> test_case.TestCase:
149-
"""Construct one test case for verification.
150-
151-
When deterministic is true, the test case is a deterministic signature.
152-
When deterministic is false, the test case is some other valid signature.
153-
"""
154-
signature = key.sign_message(message, deterministic=deterministic)
155-
tc = test_case.TestCase()
156-
tc.set_function(api.function('verify_pure', key.kl))
157-
tc.set_dependencies([f'TF_PSA_CRYPTO_PQCP_MLDSA_{key.kl}_ENABLED'])
158-
tc.set_arguments(api.metadata_arguments(key.kl, False, True) + [
159-
test_case.hex_string(key.public),
160-
test_case.hex_string(message),
161-
test_case.hex_string(signature),
162-
] + api.final_arguments())
163-
variant = "deterministic" if deterministic else "randomized"
164-
tc.set_description(f'MLDSA-{key.kl} verify {variant} {descr}')
165-
return tc
166-
167-
def gen_mldsa_pure(api: API, kl: int) -> Iterable[test_case.TestCase]:
168-
"""Generate all test cases for pure ML-DSA signature and verification."""
169-
for i, key in enumerate(KEYS[kl], 1):
170-
yield one_mldsa_sign_deterministic_pure(api, key, MESSAGES[0][0],
171-
f'key#{i}')
172-
for message, descr in MESSAGES[1:]:
173-
yield one_mldsa_sign_deterministic_pure(api, KEYS[kl][0], message,
174-
f'key#1 {descr}')
175-
for i, key in enumerate(KEYS[kl], 1):
176-
yield one_mldsa_verify_pure(api, key, MESSAGES[0][0], True,
177-
f'key#{i}')
178-
for message, descr in MESSAGES[1:]:
179-
yield one_mldsa_verify_pure(api, KEYS[kl][0], message, True,
180-
f'key#1 {descr}')
181-
for i, key in enumerate(KEYS[kl], 1):
182-
yield one_mldsa_verify_pure(api, key, MESSAGES[0][0], False,
183-
f'key#{i}')
184-
for message, descr in MESSAGES[1:]:
185-
yield one_mldsa_verify_pure(api, KEYS[kl][0], message, False,
186-
f'key#1 {descr}')
187-
188-
def gen_pqcp_mldsa_all() -> Iterable[test_case.TestCase]:
189-
"""Generate all test cases for mldsa-native."""
190-
api = PQCPAPI()
191-
for kl in sorted(KEYS.keys()):
192-
yield from gen_pqcp_key_management(kl)
193-
yield from gen_mldsa_pure(api, kl)
13+
from mbedtls_maintainer import mldsa_test_generator
19414

19515
class MLDSATestGenerator(test_data_generation.TestGenerator):
19616
"""Generate test cases for ML-DSA."""
19717

19818
def __init__(self, settings) -> None:
19919
self.targets = {
200-
'test_suite_pqcp_mldsa.dilithium_py': gen_pqcp_mldsa_all,
20+
'test_suite_pqcp_mldsa.dilithium_py': mldsa_test_generator.gen_pqcp_mldsa_all,
20121
}
20222
super().__init__(settings)
20323

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# This file needs to exist to make mbedtls_maintainer a package.
2+
# Among other things, this allows modules in this directory to make
3+
# relative imports.

0 commit comments

Comments
 (0)