diff --git a/scripts/generate_bignum_tests.py b/scripts/generate_bignum_tests.py index 68ad42f48..cfafc5658 100755 --- a/scripts/generate_bignum_tests.py +++ b/scripts/generate_bignum_tests.py @@ -43,6 +43,7 @@ of BaseTarget in test_data_generation.py. # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later import sys +import math from abc import ABCMeta from typing import List @@ -121,6 +122,71 @@ class BignumOperation(bignum_common.OperationCommon, BignumTarget, return tmp +class BignumGCDInvModOperation(BignumOperation): + #pylint: disable=abstract-method + """Common features for testing GCD and Invmod functions.""" + def __init__(self, val_a: str, val_b: str) -> None: + super().__init__(val_a=val_a, val_b=val_b) + + def description_suffix(self) -> str: + comparison_symbol = '=' + if abs(self.int_a) > abs(self.int_b): + comparison_symbol = '>' + elif abs(self.int_a) < abs(self.int_b): + comparison_symbol = '<' + suffix_parts = [ + f"|A|{comparison_symbol}|N|", + *(["A<0"] if self.int_a < 0 else []), + *(["N<0"] if self.int_b < 0 else []), + "A=0" if self.int_a == 0 else f"A {'even' if self.int_a % 2 == 0 else 'odd'}", + "N=0" if self.int_b == 0 else f"B {'even' if self.int_b % 2 == 0 else 'odd'}" + ] + return ": " + ", ".join(suffix_parts) + + # The default values from BignumOperation are not useful, so overwrite them. + input_values = [ + "c79e27fc71c69a08b3e85bd48b9cd3be9aa8e2e56df39f4ed8", + "299dd34be98436729eb10f690f8d2bfc5bee21984b775e1e75", + "-ecbb3a4e986d488172ecd54f7bd71bd18050c4ed", + "7da9ec44f42e6311c56a", + "cdbcce3f763819345cfb", + "100000000", "300000000", "500000000", + "50000", "30000", + "1", "2", "3", "", "00", "-1" + ] + input_cases = [ + ("bc7fa9fb389618302e8b", "d49730e586607d42269f"), + ("28bcc01a2d54b174532e", "d1915057d829a934c25d"), + ("d56b50834719280dfa1d", "f007b78f6278ebcccd57"), + ("8c327d1d8743c89d4483", "aa20b0c1f97a428311b5"), + ("e905382f38", "c844b4f9bdaa5ed0002df3dbd2991cd9b9d"), + ("e4623ef13d", "f2a4894ede013e354e481fe8974e67"), + ("9f6afa8bdb", "b50aa03a7066df6f27bd6267b"), + ("95f99b7122", "e8c74031ec75839f7539"), + ("32", "948fbec067"), + ("7445", "948fbec067"), + ("31850e", "948fbec067"), + ("421c2cc8", "948fbec067"), + ("32a69", "71e107"), + ("36d4e9", "3e05d1"), + ("babf01", "1bf699d1"), + ("7", "31"), + ] + + def get_return_code_gcd_modinv_odd_gcd_only(self) -> str: + code = "0" + if (self.int_a > self.int_b) or \ + (self.int_a < 0) or \ + (self.int_b % 2 == 0): + code = "MBEDTLS_ERR_MPI_BAD_INPUT_DATA" + return code + + def get_return_code_gcd_modinv_odd(self) -> str: + if self.int_b < 2: + return "MBEDTLS_ERR_MPI_BAD_INPUT_DATA" + return self.get_return_code_gcd_modinv_odd_gcd_only() + + class BignumCmp(BignumOperation): """Test cases for bignum value comparison.""" count = 0 @@ -152,6 +218,188 @@ class BignumCmpAbs(BignumCmp): super().__init__(val_a.strip("-"), val_b.strip("-")) +class BignumInvMod(BignumOperation): + """Test cases for bignum modular inverse.""" + count = 0 + symbol = "^-1 mod" + test_function = "mpi_inv_mod" + test_name = "MPI inv_mod" + # The default values are not very useful here, so clear them. + input_values = [] # type: List[str] + input_cases = bignum_common.combination_two_lists( + # Input values for A + bignum_common.expand_list_negative([ + "aa4df5cb14b4c31237f98bd1faf527c283c2d0f3eec89718664ba33f9762907c", + "f847e7731a2687c837f6b825f2937d997bf66814d3db79b27b", + "2ec0888f", + "22fbdf4c", + "32cf9a75", + ]), + # Input values for N - must be positive. + [ + "fffbbd660b94412ae61ead9c2906a344116e316a256fd387874c6c675b1d587d", + "2fe72fa5c05bc14c1279e37e2701bd956822999f42c5cbe84", + "2ec0888f", + "22fbdf4c", + "34d0830", + "364b6729", + "14419cd", + ], + ) + + def __init__(self, val_a: str, val_b: str) -> None: + super().__init__(val_a, val_b) + if math.gcd(self.int_a, self.int_b) == 1: + self._result = bignum_common.invmod_positive(self.int_a, self.int_b) + else: + self._result = -1 # No modular inverse. + + def description_suffix(self) -> str: + suffix = ": " + # Assuming N (int_b) is always positive, compare absolute values, + # but only print the absolute value bars when A is negative. + a_str = "A" if (self.int_a >= 0) else "|A|" + if abs(self.int_a) > self.int_b: + suffix += f"{a_str}>N" + elif abs(self.int_a) < self.int_b: + suffix += f"{a_str} List[str]: + if self._result == -1: # No modular inverse. + return [bignum_common.quote_str("0"), "MBEDTLS_ERR_MPI_NOT_ACCEPTABLE"] + return [bignum_common.quote_str("{:x}".format(self._result)), "0"] + + +class BignumGCD(BignumOperation): + """Test cases for greatest common divisor.""" + count = 0 + symbol = "GCD" + test_function = "mpi_gcd" + test_name = "GCD" + # The default values are not very useful here, so overwrite them. + input_values = bignum_common.expand_list_negative([ + "3c094fd6b36ee4902c8ba84d13a401def90a2130116dad3361", + "b2b06ebe14a185a83d5d2d7bddd1dd0e05e800d6b914fbed4e", + "203265b387", + "9bc8e63852", + "100000000", + "300000000", + "500000000", + "50000", + "30000", + "1", + "2", + "3", + ]) + + def __init__(self, val_a: str, val_b: str) -> None: + super().__init__(val_a, val_b) + # We always expect a positive result as the test data + # does not contain zero. + self._result = math.gcd(self.int_a, self.int_b) + + def description_suffix(self) -> str: + suffix = ": " + if abs(self.int_a) > abs(self.int_b): + suffix += "|A|>|B|" + elif abs(self.int_a) < abs(self.int_b): + suffix += "|A|<|B|" + else: + suffix += "|A|=|B|" + if self.int_a < 0: + suffix += ", A<0" + if self.int_b < 0: + suffix += ", B<0" + suffix += ", A even" if (self.int_a % 2 == 0) else ", A odd" + suffix += ", B even" if (self.int_b % 2 == 0) else ", B odd" + return suffix + + def result(self) -> List[str]: + return [bignum_common.quote_str("{:x}".format(self._result))] + + +class BignumGCDModInvOdd(BignumGCDInvModOperation): + """Test cases for both modular inverse and greatest common divisor.""" + count = 0 + symbol = "GCD & ^-1 mod" + test_function = "mpi_gcd_modinv_odd_both" + test_name = "GCD & mod inv" + + def __init__(self, val_a: str, val_b: str) -> None: + super().__init__(val_a, val_b) + self._result_code = self.get_return_code_gcd_modinv_odd() + self._result_gcd = math.gcd(self.int_a, self.int_b) + # Only compute the modular inverse if we will get a result - negative + # and zero Ns are also present in the test data so skip them too. + if self._result_gcd == 1 and self.int_b > 1: + self._result_invmod = \ + bignum_common.invmod_positive(self.int_a, self.int_b) # type: int | None + else: + self._result_invmod = None # No inverse + + def result(self) -> List[str]: + # The test requires us to tell it if there is no modular inverse. + if self._result_invmod is None: + result_invmod = "no_inverse" + else: + result_invmod = "{:x}".format(self._result_invmod) + return [ + self.format_result(self._result_gcd), + bignum_common.quote_str(result_invmod), + self._result_code, + ] + + +class BignumGCDModInvOddOnlyGCD(BignumGCDInvModOperation): + """Test cases for greatest common divisor only.""" + count = 0 + symbol = "GCD" + test_function = "mpi_gcd_modinv_odd_only_gcd" + test_name = "GCD only" + + def __init__(self, val_a: str, val_b: str) -> None: + super().__init__(val_a, val_b) + self._result_code = self.get_return_code_gcd_modinv_odd_gcd_only() + # We always expect a positive result as the function should reject + # negative inputs. + self._result_gcd = math.gcd(self.int_a, self.int_b) + + def result(self) -> List[str]: + return [self.format_result(self._result_gcd), self._result_code] + + +class BignumGCDModInvOddOnlyModInv(BignumGCDInvModOperation): + """Test cases for modular inverse only.""" + count = 0 + symbol = "^-1 mod" + test_function = "mpi_gcd_modinv_odd_only_modinv" + test_name = "Mod inv only" + + def __init__(self, val_a: str, val_b: str) -> None: + super().__init__(val_a, val_b) + self._result_code = self.get_return_code_gcd_modinv_odd() + # Only compute the modular inverse if we will get a result - negative + # and zero Ns are also present in the test data so skip them too. + if math.gcd(self.int_a, self.int_b) == 1 and self.int_b > 1: + self._result_invmod = \ + bignum_common.invmod_positive(self.int_a, self.int_b) # type: int | None + else: + self._result_invmod = None # No inverse + + def result(self) -> List[str]: + # The test requires us to tell it if there is no modular inverse. + if self._result_invmod is None: + return [bignum_common.quote_str("no_inverse"), self._result_code] + return [self.format_result(self._result_invmod), self._result_code] + + class BignumAdd(BignumOperation): """Test cases for bignum value addition.""" count = 0 diff --git a/scripts/mbedtls_framework/bignum_common.py b/scripts/mbedtls_framework/bignum_common.py index eebc858b2..3cec3fd4d 100644 --- a/scripts/mbedtls_framework/bignum_common.py +++ b/scripts/mbedtls_framework/bignum_common.py @@ -19,8 +19,7 @@ T = TypeVar('T') #pylint: disable=invalid-name def invmod(a: int, n: int) -> int: """Return inverse of a to modulo n. - Equivalent to pow(a, -1, n) in Python 3.8+. Implementation is equivalent - to long_invmod() in CPython. + Warning: the output might be negative! See invmod_positive(). """ b, c = 1, 0 while n: @@ -32,7 +31,10 @@ def invmod(a: int, n: int) -> int: raise ValueError("Not invertible") def invmod_positive(a: int, n: int) -> int: - """Return a non-negative inverse of a to modulo n.""" + """Return a non-negative inverse of a to modulo n. + + Equivalent to pow(a, -1, n) in Python 3.8+. + """ inv = invmod(a, n) return inv if inv >= 0 else inv + n @@ -66,6 +68,14 @@ def combination_pairs(values: List[T]) -> List[Tuple[T, T]]: """Return all pair combinations from input values.""" return [(x, y) for x in values for y in values] +def combination_two_lists(first_vals: List[T], second_vals: List[T]) -> List[Tuple[T, T]]: + """Return all pair combinations from two input lists""" + return [(x, y) for x in first_vals for y in second_vals] + +def expand_list_negative(values: List[str]) -> List[str]: + """Adds the negative of every element in the list to the list""" + return values + [f"-{value}" for value in values] + def bits_to_limbs(bits: int, bits_in_limb: int) -> int: """ Return the appropriate ammount of limbs needed to store a number contained in input bits""" diff --git a/scripts/mbedtls_framework/bignum_core.py b/scripts/mbedtls_framework/bignum_core.py index 164da0562..3f798fc3c 100644 --- a/scripts/mbedtls_framework/bignum_core.py +++ b/scripts/mbedtls_framework/bignum_core.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later # +import math import random from typing import Dict, Iterator, List, Tuple @@ -10,7 +11,7 @@ from typing import Dict, Iterator, List, Tuple from . import test_case from . import test_data_generation from . import bignum_common -from .bignum_data import ADD_SUB_DATA +from . import bignum_data class BignumCoreTarget(test_data_generation.BaseTarget): #pylint: disable=abstract-method, too-few-public-methods @@ -166,7 +167,7 @@ class BignumCoreAddAndAddIf(BignumCoreTarget, bignum_common.OperationCommon): test_function = "mpi_core_add_and_add_if" test_name = "mpi_core_add_and_add_if" input_style = "arch_split" - input_values = ADD_SUB_DATA + input_values = bignum_data.ADD_SUB_DATA unique_combinations_only = True def result(self) -> List[str]: @@ -187,7 +188,7 @@ class BignumCoreSub(BignumCoreTarget, bignum_common.OperationCommon): symbol = "-" test_function = "mpi_core_sub" test_name = "mbedtls_mpi_core_sub" - input_values = ADD_SUB_DATA + input_values = bignum_data.ADD_SUB_DATA def result(self) -> List[str]: if self.int_a >= self.int_b: @@ -894,3 +895,78 @@ class BignumCoreZeroCheckCT(BignumCoreTarget, bignum_common.OperationCommon): def result(self) -> List[str]: result = 1 if self.int_a == 0 else 0 return [str(result)] + +class BignumCoreGcdModinvOdd(BignumCoreTarget, test_data_generation.BaseTest): + """Test cases for bignum core GCD+modinv (odd modulus)""" + + test_function = "mpi_core_gcd_modinv_odd" + test_name = "mpi_core_gcd_modinv_odd" + + # - All small integers because that naturally covers a lot of cases. + # - (Close to) powers of 2 because that looks like interesting values. + # Also covers the cases where N, N+1 or N-1 is a multiple of A (with + # multiple limbs). + # - X * 2, X * 3 is so that we get GCD(X*2, X*3) = X where the GCD has a + # the same order of magnitude as the inputs. + # - Random values of cryptographic size for good measure. + DATA = ( + ("0", 0), + ("1", 1), + ("2", 2), + ("3", 3), + ("4", 4), + ("5", 5), + ("6", 6), + ("7", 7), + ("2^64 - 1", 2**64 - 1), + ("2^64", 2**64), + ("2^64 + 1", 2**64 + 1), + ("2^128 - 1", 2**128 - 1), + ("2^128", 2**128), + ("2^128 + 1", 2**128 + 1), + ("prime192[1]", int(bignum_data.SAFE_PRIME_192_BIT_SEED_1, 16)), + ("prime192[1] * 2", int(bignum_data.SAFE_PRIME_192_BIT_SEED_1, 16) * 2), + ("prime192[1] * 3", int(bignum_data.SAFE_PRIME_192_BIT_SEED_1, 16) * 3), + ("rand192[2.1]", int(bignum_data.RANDOM_192_BIT_SEED_2_NO1, 16)), + ("rand192[2.2]", int(bignum_data.RANDOM_192_BIT_SEED_2_NO2, 16)), + ("rand192[2.3]", int(bignum_data.RANDOM_192_BIT_SEED_2_NO3, 16)), + ("rand192[2.4]", int(bignum_data.RANDOM_192_BIT_SEED_2_NO4, 16)), + ("rand192[2.9]", int(bignum_data.RANDOM_192_BIT_SEED_2_NO9, 16)), + ("prime1024[3]", int(bignum_data.SAFE_PRIME_1024_BIT_SEED_3, 16)), + ("rand1024[4.1]", int(bignum_data.RANDOM_1024_BIT_SEED_4_NO1, 16)), + ("rand1024[4.2]", int(bignum_data.RANDOM_1024_BIT_SEED_4_NO2, 16)), + ("rand1024[4.3]", int(bignum_data.RANDOM_1024_BIT_SEED_4_NO3, 16)), + ("rand1024[4.4]", int(bignum_data.RANDOM_1024_BIT_SEED_4_NO4, 16)), + ("rand1024[4.5]", int(bignum_data.RANDOM_1024_BIT_SEED_4_NO5, 16)), + ("rand1024[4.5] * 2", int(bignum_data.RANDOM_1024_BIT_SEED_4_NO5, 16) * 2), + ("rand1024[4.5] * 3", int(bignum_data.RANDOM_1024_BIT_SEED_4_NO5, 16) * 3), + ) + + def __init__(self, a: int, a_desc: str, n: int, n_desc: str) -> None: + self.a_val = a + self.a_desc = a_desc + self.n_val = n + self.n_desc = n_desc + self.g_val = math.gcd(a, n) + test_i = self.g_val == 1 and self.n_val != 1 + self.i_val = bignum_common.invmod_positive(a, n) if test_i else None + + def arguments(self) -> List[str]: + a_str = f"{self.a_val:x}" + n_str = f"{self.n_val:x}" + g_str = f"{self.g_val:x}" + i_str = f"{self.i_val:x}" if self.i_val is not None else "" + return [bignum_common.quote_str(s) for s in (a_str, n_str, g_str, i_str)] + + def description(self) -> str: + return f"GCD-modinv, A = {self.a_desc}, N = {self.n_desc}" + + @classmethod + def generate_function_tests(cls) -> Iterator[test_case.TestCase]: + for n_desc, n in cls.DATA: + if n % 2 == 0: + continue + for a_desc, a in cls.DATA: + if a > n: + continue + yield cls(a, a_desc, n, n_desc).create_test_case() diff --git a/tests/src/bignum_helpers.c b/tests/src/bignum_helpers.c index 8b35e38c8..5c2b4de69 100644 --- a/tests/src/bignum_helpers.c +++ b/tests/src/bignum_helpers.c @@ -76,6 +76,7 @@ int mbedtls_test_read_mpi_core(mbedtls_mpi_uint **pX, size_t *plimbs, exit: mbedtls_free(*pX); + *pX = NULL; return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; }