From c57a873824705bc0ce46a1ae218d2106d4cac50a Mon Sep 17 00:00:00 2001 From: Sean Parkinson Date: Tue, 26 May 2026 12:47:34 +1000 Subject: [PATCH] sp_int: fixes and added testing sp_set_bit(): check i is in range before use. _sp_div_2(): Use a constant-time clamp as called by a constant-time function. _sp_sqr(): static buffer needs to be one larger for when ECC with P-521 is the largest size. Add tests: - Testing negative numbers with mp_read_raidx/mp_to_radix 10/16 - Testing negative numbers with mp_add_d/mp_sub_d - Testing of mp_gcd without mp_lcm - More testing of mp_mod_d and when negative numbers are used - Check maximum values work for square. Check of _sp_sqr() bug - Add testing of mp_add/sub_mod_ct - Add testing of mp_cmp_mag - Add testing of mp_mulmod/mp_sqrmod - Add testing of mp_exch - Add testing of mp_to_unsigned_bin_len_ct - Add testing of mp_exptmod that uses base-2 windowing method. - Add testing of mp_invmod_mont_ct --- wolfcrypt/src/sp_int.c | 20 +- wolfcrypt/test/test.c | 629 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 632 insertions(+), 17 deletions(-) diff --git a/wolfcrypt/src/sp_int.c b/wolfcrypt/src/sp_int.c index 19d64a13c1..11fdd449e0 100644 --- a/wolfcrypt/src/sp_int.c +++ b/wolfcrypt/src/sp_int.c @@ -6143,6 +6143,13 @@ int sp_set_bit(sp_int* a, int i) /* Get index of word to set. */ sp_size_t w = (sp_size_t)(i >> SP_WORD_SHIFT); +#if SP_INT_DIGITS < (65536 / SP_WORD_SIZEOF) + /* Check bit index isn't bigger than maximum allowed. */ + if (i > SP_INT_DIGITS * SP_WORD_SIZE) { + err = MP_VAL; + } + else +#endif /* Check for valid number and space for bit. */ if ((a == NULL) || (i < 0) || (w >= a->size)) { err = MP_VAL; @@ -7408,9 +7415,7 @@ static void _sp_div_2(const sp_int* a, sp_int* r) /* Last word only needs to be shifted down. */ r->dp[i] = a->dp[i] >> 1; /* Set used to be all words seen. */ - r->used = (sp_size_t)(i + 1); - /* Remove leading zeros. */ - sp_clamp(r); + r->used = (sp_size_t)(i + 1 - (int)((r->dp[i] - 1) >> (SP_WORD_SIZE - 1))); #ifdef WOLFSSL_SP_INT_NEGATIVE /* Same sign in result. */ r->sign = a->sign; @@ -7892,7 +7897,7 @@ int sp_sub(const sp_int* a, const sp_int* b, sp_int* r) else { /* Reverse subtract absolute values and use opposite sign of a */ _sp_sub_off(b, a, r, 0); - r->sign = 1 - a->sign; + r->sign = (sp_sign_t)(1 - a->sign); } #endif } @@ -8049,7 +8054,8 @@ static int _sp_submod(const sp_int* a, const sp_int* b, const sp_int* m, FREE_SP_INT(t0, NULL); FREE_SP_INT(t1, NULL); #else /* WOLFSSL_SP_INT_NEGATIVE */ - sp_size_t used = ((a->used >= b->used) ? a->used + 1 : b->used + 1); + sp_size_t used = (sp_size_t)((a->used >= b->used) ? a->used + 1 : + b->used + 1); DECL_SP_INT(t, used); ALLOC_SP_INT_SIZE(t, used, err, NULL); @@ -9199,7 +9205,7 @@ static int _sp_mod(const sp_int* a, const sp_int* m, sp_int* r) /* In case remainder is modulus - allocate temporary. */ ALLOC_SP_INT(t, a->used + 1, err, NULL); if (err == MP_OKAY) { - _sp_init_size(t, a->used + 1); + _sp_init_size(t, (sp_size_t)(a->used + 1)); /* Use divide to calculate remainder and don't get quotient. */ err = sp_div(a, m, NULL, t); } @@ -15056,7 +15062,7 @@ static int _sp_sqr(const sp_int* a, sp_int* r) #elif defined(WOLFSSL_SP_DYN_STACK) sp_int_digit t[((a->used + 1) / 2) * 2 + 1]; #else - sp_int_digit t[(SP_INT_DIGITS + 1) / 2]; + sp_int_digit t[(SP_INT_DIGITS + 1) / 2 + 1]; #endif #if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_SP_NO_MALLOC) diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 1f8ce25537..879f61a525 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -67422,6 +67422,24 @@ static wc_test_ret_t mp_test_radix_10(mp_int* a, mp_int* r, WC_RNG* rng) if (!mp_iszero(r)) return WC_TEST_RET_ENC_NC; +#ifdef WOLFSSL_SP_INT_NEGATIVE + /* Negative values are written with a leading '-' and read back. */ + ret = mp_set(a, 12345); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + mp_setneg(a); + ret = mp_toradix(a, str, MP_RADIX_DEC); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (str[0] != '-') + return WC_TEST_RET_ENC_NC; + ret = mp_read_radix(r, str, MP_RADIX_DEC); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (mp_cmp(r, a) != MP_EQ) + return WC_TEST_RET_ENC_NC; +#endif + return 0; } #endif @@ -67506,6 +67524,24 @@ static wc_test_ret_t mp_test_radix_16(mp_int* a, mp_int* r, WC_RNG* rng) if (!mp_iszero(r)) return WC_TEST_RET_ENC_NC; +#ifdef WOLFSSL_SP_INT_NEGATIVE + /* Negative values are written with a leading '-' and read back. */ + ret = mp_set(a, 0xABCD); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + mp_setneg(a); + ret = mp_toradix(a, str, MP_RADIX_HEX); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (str[0] != '-') + return WC_TEST_RET_ENC_NC; + ret = mp_read_radix(r, str, MP_RADIX_HEX); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (mp_cmp(r, a) != MP_EQ) + return WC_TEST_RET_ENC_NC; +#endif + #ifdef WOLFSSL_SP_MATH ret = mp_toradix(a, str, 8); if (ret != WC_NO_ERR_TRACE(MP_VAL)) @@ -67563,11 +67599,13 @@ static wc_test_ret_t mp_test_shift(mp_int* a, mp_int* r1, WC_RNG* rng) #if !(defined(WOLFSSL_SP_MATH_ALL) || defined(WOLFSSL_SP_MATH)) || \ (defined(WOLFSSL_SP_ADD_D) && defined(WOLFSSL_SP_SUB_D)) -static wc_test_ret_t mp_test_add_sub_d(mp_int* a, mp_int* r1) +static wc_test_ret_t mp_test_add_sub_d(mp_int* a, mp_int* b, mp_int* r1) { int i, j; wc_test_ret_t ret; + (void)b; + for (i = 0; i <= DIGIT_BIT * 2; i++) { mp_zero(a); mp_set_bit(a, i); @@ -67600,6 +67638,62 @@ static wc_test_ret_t mp_test_add_sub_d(mp_int* a, mp_int* r1) if (r1->used != 0) return WC_TEST_RET_ENC_NC; +#ifdef WOLFSSL_SP_INT_NEGATIVE + /* Adding/subtracting a digit to/from a negative value, and producing a + * negative result, exercise the signed digit paths. */ + /* -100 + 5 = -95 */ + ret = mp_set(a, 100); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + mp_setneg(a); + ret = mp_add_d(a, 5, r1); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + ret = mp_set(b, 95); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + mp_setneg(b); + if (mp_cmp(r1, b) != MP_EQ) + return WC_TEST_RET_ENC_NC; + /* -3 + 10 = 7 */ + ret = mp_set(a, 3); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + mp_setneg(a); + ret = mp_add_d(a, 10, r1); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (mp_cmp_d(r1, 7) != MP_EQ) + return WC_TEST_RET_ENC_NC; + /* -5 - 3 = -8 */ + ret = mp_set(a, 5); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + mp_setneg(a); + ret = mp_sub_d(a, 3, r1); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + ret = mp_set(b, 8); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + mp_setneg(b); + if (mp_cmp(r1, b) != MP_EQ) + return WC_TEST_RET_ENC_NC; + /* 3 - 10 = -7 */ + ret = mp_set(a, 3); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + ret = mp_sub_d(a, 10, r1); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + ret = mp_set(b, 7); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + mp_setneg(b); + if (mp_cmp(r1, b) != MP_EQ) + return WC_TEST_RET_ENC_NC; +#endif /* WOLFSSL_SP_INT_NEGATIVE */ + return 0; } #endif @@ -68762,7 +68856,7 @@ static wc_test_ret_t mp_test_set_is_bit(mp_int* a) } #endif -#ifdef WOLFSSL_SP_MATH +#if defined(WOLFSSL_SP_MATH) || defined(WOLFSSL_SP_MATH_ALL) mp_zero(a); for (j = 1; j <= 3; j++) { i = SP_INT_MAX_BITS - j; @@ -69264,19 +69358,28 @@ static wc_test_ret_t mp_test_prime(mp_int* a, WC_RNG* rng) } #endif -#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) && !defined(WC_RSA_BLINDING) +/* mp_gcd has less restrictive protections than mp_lcm. + * Test mp_gcd with and without mp_lcm. */ +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) +#if !defined(WC_RSA_BLINDING) || defined(HAVE_FIPS) || defined(HAVE_SELFTEST) + #define MP_TEST_HAVE_LCM +#endif static wc_test_ret_t mp_test_lcm_gcd(mp_int* a, mp_int* b, mp_int* r, mp_int* exp, WC_RNG* rng) { wc_test_ret_t ret; +#ifdef MP_TEST_HAVE_LCM int i; WOLFSSL_SMALL_STACK_STATIC const int kat[][3] = { { 1, 1, 1 }, { 2, 1, 2 }, { 1, 2, 2 }, { 2, 4, 4 }, { 4, 2, 4 }, { 12, 56, 168 }, { 56, 12, 168 } }; +#endif (void)exp; + (void)rng; +#ifdef MP_TEST_HAVE_LCM mp_set(a, 0); mp_set(b, 1); ret = mp_lcm(a, a, r); @@ -69301,7 +69404,6 @@ static wc_test_ret_t mp_test_lcm_gcd(mp_int* a, mp_int* b, mp_int* r, mp_int* ex return WC_TEST_RET_ENC_NC; } - (void)rng; #if defined(WOLFSSL_KEY_GEN) && (!defined(NO_DH) || !defined(NO_DSA)) && \ !defined(WC_NO_RNG) ret = mp_rand_prime(a, 20, rng, NULL); @@ -69326,7 +69428,9 @@ static wc_test_ret_t mp_test_lcm_gcd(mp_int* a, mp_int* b, mp_int* r, mp_int* ex if (ret != MP_EQ) return WC_TEST_RET_ENC_NC; #endif +#endif /* MP_TEST_HAVE_LCM */ + /* GCD with zero - returns the non-zero operand, zero/zero is invalid. */ mp_set(a, 11); mp_zero(b); ret = mp_gcd(a, b, r); @@ -69345,6 +69449,57 @@ static wc_test_ret_t mp_test_lcm_gcd(mp_int* a, mp_int* b, mp_int* r, mp_int* ex if (ret != WC_NO_ERR_TRACE(MP_VAL)) return WC_TEST_RET_ENC_EC(ret); + /* Multi-digit GCD exercising the Euclidean reduction loops. + * Uses the identity gcd(2^m - 1, 2^n - 1) = 2^gcd(m,n) - 1. + * gcd(2^192-1, 2^128-1) = 2^64-1: both operands multi-word so the + * smaller is reduced with the multi-word sp_mod() path. */ + ret = mp_set(a, 1); + if (ret == MP_OKAY) + ret = mp_mul_2d(a, 192, a); + if (ret == MP_OKAY) + ret = mp_sub_d(a, 1, a); + if (ret == MP_OKAY) + ret = mp_set(b, 1); + if (ret == MP_OKAY) + ret = mp_mul_2d(b, 128, b); + if (ret == MP_OKAY) + ret = mp_sub_d(b, 1, b); + if (ret == MP_OKAY) + ret = mp_set(exp, 1); + if (ret == MP_OKAY) + ret = mp_mul_2d(exp, 64, exp); + if (ret == MP_OKAY) + ret = mp_sub_d(exp, 1, exp); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + /* a > b: exercises the operand swap. */ + ret = mp_gcd(a, b, r); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (mp_cmp(r, exp) != MP_EQ) + return WC_TEST_RET_ENC_NC; + /* b < a: no swap. */ + ret = mp_gcd(b, a, r); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (mp_cmp(r, exp) != MP_EQ) + return WC_TEST_RET_ENC_NC; + + /* gcd(2^192-1, 2^64-1) = 2^64-1: smaller operand is a single word so + * the sp_mod_d() reduction path is exercised. */ + ret = mp_set(b, 1); + if (ret == MP_OKAY) + ret = mp_mul_2d(b, 64, b); + if (ret == MP_OKAY) + ret = mp_sub_d(b, 1, b); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + ret = mp_gcd(a, b, r); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (mp_cmp(r, exp) != MP_EQ) + return WC_TEST_RET_ENC_NC; + return 0; } #endif @@ -69479,14 +69634,47 @@ static wc_test_ret_t mp_test_mod_d(mp_int* a, WC_RNG* rng) if (r != rem) return WC_TEST_RET_ENC_NC; } + + /* Power-of-2 divisor uses the bitmask fast path. */ + ret = mp_set(a, 0x1234); + if (ret == MP_OKAY) + ret = mp_mod_d(a, 8, &r); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (r != (0x1234 & 7)) + return WC_TEST_RET_ENC_NC; + + /* Divisor of 10 has a dedicated multi-word fast path. */ + ret = randNum(a, MP_MAX_TEST_BYTE_LEN, rng, NULL); + if (ret == MP_OKAY) + ret = mp_mod_d(a, 10, &r); + if (ret == MP_OKAY) + ret = mp_div_d(a, 10, a, &rem); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (r != rem) + return WC_TEST_RET_ENC_NC; + +#ifdef WOLFSSL_SP_INT_NEGATIVE + /* Test a negative value. */ + ret = mp_set(a, 17); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + mp_setneg(a); + ret = mp_mod_d(a, 5, &r); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (r != 3) + return WC_TEST_RET_ENC_NC; +#endif #endif return 0; } #endif -static wc_test_ret_t mp_test_mul_sqr(mp_int* a, mp_int* b, mp_int* r1, mp_int* r2, - WC_RNG* rng) +static wc_test_ret_t mp_test_mul_sqr(mp_int* a, mp_int* b, mp_int* r1, + mp_int* r2, WC_RNG* rng) { wc_test_ret_t ret; int i; @@ -69567,11 +69755,270 @@ static wc_test_ret_t mp_test_mul_sqr(mp_int* a, mp_int* b, mp_int* r1, mp_int* r return WC_TEST_RET_ENC_EC(ret); #endif /* HAVE_ECC && (ECC_SHAMIR || FP_ECC) */ #endif /* WOLFSSL_SP_MATH_ALL || WOLFSSL_HAVE_SP_DH || (HAVE_ECC && FP_ECC) */ + + /* Check that the maximum value squared works. */ + ret = mp_2expt(a, SP_INT_DIGITS / 2 * SP_WORD_SIZE); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + ret = mp_set(b, 1); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + ret = mp_sub(a, b, a); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + ret = mp_mul(a, a, r1); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + ret = mp_sqr(a, r2); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (mp_cmp(r1, r2) != MP_EQ) + return WC_TEST_RET_ENC_NC; #endif /* WOLFSSL_SP_MATH_ALL */ return 0; } +#if defined(WOLFSSL_SP_MATH_ALL) && defined(HAVE_ECC) +/* Functional tests for the constant-time modular arithmetic used by ECC: + * mp_addmod_ct, mp_submod_ct and mp_div_2_mod_ct. These are not reached by + * the parameter-validation tests so their computation paths are exercised + * here against their variable-time equivalents. */ +static wc_test_ret_t mp_test_addsubmod_ct(mp_int* a, mp_int* b, mp_int* m, + mp_int* r, mp_int* t, WC_RNG* rng) +{ + wc_test_ret_t res = 0; + int ret; + int i; + + for (i = 0; (i < 16) && (res == 0); i++) { + /* Odd, multi-word modulus (2 must be invertible for div_2_mod_ct). */ + ret = randNum(m, 24, rng, NULL); + if (ret == MP_OKAY) + ret = mp_set_bit(m, 0); /* force modulus odd */ + /* Operands reduced to the range [0, m). */ + if (ret == MP_OKAY) + ret = randNum(a, 24, rng, NULL); + if (ret == MP_OKAY) + ret = mp_mod(a, m, a); + if (ret == MP_OKAY) + ret = randNum(b, 24, rng, NULL); + if (ret == MP_OKAY) + ret = mp_mod(b, m, b); + if (ret != MP_OKAY) { + res = WC_TEST_RET_ENC_EC(ret); + break; + } + + /* addmod_ct must match the variable-time addmod. */ + ret = mp_addmod_ct(a, b, m, r); + if (ret == MP_OKAY) + ret = mp_addmod(a, b, m, t); + if (ret != MP_OKAY) { + res = WC_TEST_RET_ENC_EC(ret); + break; + } + if (mp_cmp(r, t) != MP_EQ) { + res = WC_TEST_RET_ENC_NC; + break; + } + + /* submod_ct: (a - b) mod m, verified by adding b back gives a. */ + ret = mp_submod_ct(a, b, m, r); + if (ret == MP_OKAY) + ret = mp_addmod_ct(r, b, m, t); + if (ret != MP_OKAY) { + res = WC_TEST_RET_ENC_EC(ret); + break; + } + if (mp_cmp(t, a) != MP_EQ) { + res = WC_TEST_RET_ENC_NC; + break; + } + + /* div_2_mod_ct: r = a/2 mod m, verified by doubling: 2*r mod m == a. */ + ret = mp_div_2_mod_ct(a, m, r); + if (ret == MP_OKAY) + ret = mp_addmod_ct(r, r, m, t); + if (ret != MP_OKAY) { + res = WC_TEST_RET_ENC_EC(ret); + break; + } + if (mp_cmp(t, a) != MP_EQ) { + res = WC_TEST_RET_ENC_NC; + break; + } + } + + return res; +} +#endif /* WOLFSSL_SP_MATH_ALL && HAVE_ECC */ + +#if defined(WOLFSSL_SP_MATH_ALL) && !defined(WOLFSSL_RSA_PUBLIC_ONLY) +/* Exercises mp_cmp_mag - magnitude comparison (sign-independent) including + * NULL operands. */ +static wc_test_ret_t mp_test_cmp_mag(mp_int* a, mp_int* b) +{ + int ret; + + ret = mp_set(a, 7); + if (ret == MP_OKAY) + ret = mp_set(b, 5); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + + /* NULL operands: both NULL are equal, a non-NULL has greater magnitude. */ + if (mp_cmp_mag(NULL, NULL) != MP_EQ) + return WC_TEST_RET_ENC_NC; + if (mp_cmp_mag(a, NULL) != MP_GT) + return WC_TEST_RET_ENC_NC; + if (mp_cmp_mag(NULL, a) != MP_LT) + return WC_TEST_RET_ENC_NC; + + /* Same value and differing magnitudes. */ + if (mp_cmp_mag(a, a) != MP_EQ) + return WC_TEST_RET_ENC_NC; + if (mp_cmp_mag(a, b) != MP_GT) + return WC_TEST_RET_ENC_NC; + if (mp_cmp_mag(b, a) != MP_LT) + return WC_TEST_RET_ENC_NC; + +#ifdef WOLFSSL_SP_INT_NEGATIVE + /* Magnitude comparison ignores sign: |-7| > |5|. */ + a->sign = MP_NEG; + if (mp_cmp_mag(a, b) != MP_GT) + return WC_TEST_RET_ENC_NC; + if (mp_cmp_mag(b, a) != MP_LT) + return WC_TEST_RET_ENC_NC; +#endif + + return 0; +} +#endif /* WOLFSSL_SP_MATH_ALL && !WOLFSSL_RSA_PUBLIC_ONLY */ + +#if defined(WOLFSSL_SP_MATH_ALL) && !defined(WOLFSSL_RSA_VERIFY_ONLY) && \ + !defined(WOLFSSL_RSA_PUBLIC_ONLY) +/* Exercises the result-aliases-modulus paths of mp_mulmod (_sp_mulmod_tmp) and + * mp_sqrmod (_sp_sqrmod), including their zero-operand short-circuits. */ +static wc_test_ret_t mp_test_mulmod_sqrmod(mp_int* a, mp_int* b, mp_int* m, + mp_int* r) +{ + int ret; + + /* mp_mulmod with the result aliasing the modulus uses the temporary + * buffer path (_sp_mulmod_tmp). */ + ret = mp_set(a, 0x3); + if (ret == MP_OKAY) + ret = mp_set(b, 0x5); + if (ret == MP_OKAY) + ret = mp_set(m, 0x65); /* modulus 101 */ + if (ret == MP_OKAY) + ret = mp_mulmod(a, b, m, r); /* reference */ + if (ret == MP_OKAY) + ret = mp_mulmod(a, b, m, m); /* result aliases modulus */ + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (mp_cmp(m, r) != MP_EQ) + return WC_TEST_RET_ENC_NC; + /* Zero operand short-circuit in _sp_mulmod_tmp. */ + ret = mp_set(m, 0x65); + if (ret == MP_OKAY) + ret = mp_set(b, 0); + if (ret == MP_OKAY) + ret = mp_mulmod(a, b, m, m); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (!mp_iszero(m)) + return WC_TEST_RET_ENC_NC; + + /* mp_sqrmod with the result aliasing the modulus uses _sp_sqrmod. */ + ret = mp_set(a, 0x7); + if (ret == MP_OKAY) + ret = mp_set(m, 0x65); + if (ret == MP_OKAY) + ret = mp_sqrmod(a, m, r); /* reference */ + if (ret == MP_OKAY) + ret = mp_set(m, 0x65); + if (ret == MP_OKAY) + ret = mp_sqrmod(a, m, m); /* result aliases modulus */ + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (mp_cmp(m, r) != MP_EQ) + return WC_TEST_RET_ENC_NC; + /* Zero operand short-circuit in _sp_sqrmod. */ + ret = mp_set(m, 0x65); + if (ret == MP_OKAY) + ret = mp_set(a, 0); + if (ret == MP_OKAY) + ret = mp_sqrmod(a, m, m); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (!mp_iszero(m)) + return WC_TEST_RET_ENC_NC; + + return 0; +} + +/* Testing mp_exch. */ +static wc_test_ret_t mp_test_exch(mp_int* a, mp_int* b, mp_int* r1, + mp_int* r2) +{ + int ret; + + /* mp_exch swaps the two values. */ + ret = mp_set(a, 1); + if (ret == MP_OKAY) + ret = mp_mul_2d(a, 200, a); /* multi-word value */ + if (ret == MP_OKAY) + ret = mp_add_d(a, 0x55, a); + if (ret == MP_OKAY) + ret = mp_set(b, 0x1234); /* single-word value */ + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + ret = mp_copy(a, r1); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + ret = mp_copy(b, r2); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + ret = mp_exch(a, b); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if ((mp_cmp(a, r2) != MP_EQ) || (mp_cmp(b, r1) != MP_EQ)) + return WC_TEST_RET_ENC_NC; + + return 0; +} + +/* Testing mp_to_unsigned_bin_len_ct against the variable-time export. */ +static wc_test_ret_t mp_test_to_unsigned_bin_len_ct(mp_int* a) +{ + int ret; + byte buf[40]; + byte buf2[40]; + + /* mp_to_unsigned_bin_len_ct must match the variable-time export. */ + ret = mp_set(a, 1); + if (ret == MP_OKAY) + ret = mp_mul_2d(a, 100, a); + if (ret == MP_OKAY) + ret = mp_add_d(a, 0xAB, a); /* multi-word value */ + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + XMEMSET(buf, 0xff, sizeof(buf)); + XMEMSET(buf2, 0xff, sizeof(buf2)); + ret = mp_to_unsigned_bin_len_ct(a, buf, (int)sizeof(buf)); + if (ret == MP_OKAY) + ret = mp_to_unsigned_bin_len(a, buf2, (int)sizeof(buf2)); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (XMEMCMP(buf, buf2, sizeof(buf)) != 0) + return WC_TEST_RET_ENC_NC; + + return 0; +} +#endif /* WOLFSSL_SP_MATH_ALL && !RSA_VERIFY_ONLY && !RSA_PUBLIC_ONLY */ + #if (!defined(NO_RSA) && \ !defined(WOLFSSL_RSA_VERIFY_ONLY) && !defined(WOLFSSL_RSA_PUBLIC_ONLY)) || \ defined(HAVE_ECC) || !defined(NO_DSA) || defined(OPENSSL_EXTRA) @@ -69690,9 +70137,15 @@ static wc_test_ret_t mp_test_invmod(mp_int* a, mp_int* m, mp_int* r) #if !defined(NO_RSA) || !defined(NO_DSA) || !defined(NO_DH) || \ (defined(HAVE_ECC) && defined(HAVE_COMP_KEY)) || defined(OPENSSL_EXTRA) -static wc_test_ret_t mp_test_exptmod(mp_int* b, mp_int* e, mp_int* m, mp_int* r) +static wc_test_ret_t mp_test_exptmod(mp_int* b, mp_int* e, mp_int* m, mp_int* r, + mp_int* t) { wc_test_ret_t ret; +#ifdef WOLFSSL_SP_MATH_ALL + int k; +#endif + + (void)t; mp_set(b, 0x2); mp_set(e, 0x3); @@ -69815,6 +70268,110 @@ static wc_test_ret_t mp_test_exptmod(mp_int* b, mp_int* e, mp_int* m, mp_int* r) return WC_TEST_RET_ENC_EC(ret); #endif +#ifdef WOLFSSL_SP_MATH_ALL + /* Larger, multi-word moduli to exercise the Montgomery, base-2 and + * windowed exponentiation paths that small single-word moduli do not + * reach. */ + + /* m = 2^521 - 1 (a Mersenne prime, multi-word and not an RSA/DH + * accelerated size); e = m - 1. By Fermat's little theorem, + * b^(m-1) mod m == 1 when gcd(b, m) == 1. */ + if ((ret = mp_set(m, 1)) != MP_OKAY || + (ret = mp_mul_2d(m, 521, m)) != MP_OKAY || + (ret = mp_sub_d(m, 1, m)) != MP_OKAY || + (ret = mp_sub_d(m, 1, e)) != MP_OKAY) { + return WC_TEST_RET_ENC_EC(ret); + } + + /* Odd, multi-word modulus, base != 2: + * mp_exptmod_ex -> constant-time Montgomery exponentiation + * mp_exptmod_nct -> windowed exponentiation (large exponent). */ + if ((ret = mp_set(b, 3)) != MP_OKAY || + (ret = mp_exptmod_ex(b, e, (int)e->used, m, r)) != MP_OKAY) { + return WC_TEST_RET_ENC_EC(ret); + } + if (!mp_isone(r)) { + return WC_TEST_RET_ENC_NC; + } + if ((ret = mp_exptmod_nct(b, e, m, r)) != MP_OKAY) { + return WC_TEST_RET_ENC_EC(ret); + } + if (!mp_isone(r)) { + return WC_TEST_RET_ENC_NC; + } + + /* Base 2 selects the dedicated base-2 exponentiation. */ + if ((ret = mp_set(b, 2)) != MP_OKAY || + (ret = mp_exptmod_ex(b, e, (int)e->used, m, r)) != MP_OKAY) { + return WC_TEST_RET_ENC_EC(ret); + } + if (!mp_isone(r)) { + return WC_TEST_RET_ENC_NC; + } + + /* Smaller exponents reach the narrower window sizes of the windowed + * non-constant-time exponentiation (a 31-bit exponent uses a 3-bit + * window, a 101-bit exponent a 4-bit window). Cross-check the + * windowed result against the constant-time Montgomery result. */ + for (k = 0; k < 2; k++) { + int ebits = (k == 0) ? 30 : 100; + if ((ret = mp_set(e, 1)) != MP_OKAY || + (ret = mp_mul_2d(e, ebits, e)) != MP_OKAY || + (ret = mp_add_d(e, 0x9, e)) != MP_OKAY || /* odd exponent */ + (ret = mp_set(b, 3)) != MP_OKAY || + (ret = mp_exptmod_ex(b, e, (int)e->used, m, r)) != MP_OKAY || + (ret = mp_exptmod_nct(b, e, m, t)) != MP_OKAY) { + return WC_TEST_RET_ENC_EC(ret); + } + if (mp_cmp(r, t) != MP_EQ) { + return WC_TEST_RET_ENC_NC; + } + } + + /* Restore exponent to m - 1 for the remaining odd-modulus case. */ + if ((ret = mp_sub_d(m, 1, e)) != MP_OKAY) { + return WC_TEST_RET_ENC_EC(ret); + } + + /* Base == modulus: base reduces to zero so the result is zero. */ + if ((ret = mp_copy(m, b)) != MP_OKAY || + (ret = mp_set(e, 5)) != MP_OKAY || + (ret = mp_exptmod_ex(b, e, (int)e->used, m, r)) != MP_OKAY) { + return WC_TEST_RET_ENC_EC(ret); + } + if (!mp_iszero(r)) { + return WC_TEST_RET_ENC_NC; + } + + /* Even, multi-word modulus uses the generic exponentiation path for + * both mp_exptmod_ex and mp_exptmod_nct. Verify b^6 mod m against + * repeated modular multiplication. */ + if ((ret = mp_set(m, 1)) != MP_OKAY || + (ret = mp_mul_2d(m, 521, m)) != MP_OKAY || /* m = 2^521 (even) */ + (ret = mp_set(b, 7)) != MP_OKAY || + (ret = mp_set(e, 6)) != MP_OKAY || + (ret = mp_set(t, 1)) != MP_OKAY) { + return WC_TEST_RET_ENC_EC(ret); + } + for (k = 0; k < 6; k++) { + if ((ret = mp_mulmod(t, b, m, t)) != MP_OKAY) { + return WC_TEST_RET_ENC_EC(ret); + } + } + if ((ret = mp_exptmod_ex(b, e, (int)e->used, m, r)) != MP_OKAY) { + return WC_TEST_RET_ENC_EC(ret); + } + if (mp_cmp(r, t) != MP_EQ) { + return WC_TEST_RET_ENC_NC; + } + if ((ret = mp_exptmod_nct(b, e, m, r)) != MP_OKAY) { + return WC_TEST_RET_ENC_EC(ret); + } + if (mp_cmp(r, t) != MP_EQ) { + return WC_TEST_RET_ENC_NC; + } +#endif /* WOLFSSL_SP_MATH_ALL */ + return 0; } #endif /* !NO_RSA || !NO_DSA || !NO_DH || (HAVE_ECC && HAVE_COMP_KEY) || @@ -69936,6 +70493,41 @@ static wc_test_ret_t mp_test_mont(mp_int* a, mp_int* m, mp_int* n, mp_int* r, WC return WC_TEST_RET_ENC_NC; } +#ifdef WOLFSSL_SP_INVMOD_MONT_CT + /* Constant-time modular inverse in Montgomery form over a multi-word + * prime exercises the table-based exponentiation in _sp_invmod_mont_ct + * that single-word moduli do not reach. */ + + /* m = 2^521 - 1 (a Mersenne prime, multi-word). */ + ret = mp_2expt(m, 521); + if (ret == MP_OKAY) + ret = mp_sub_d(m, 1, m); + if (ret == MP_OKAY) + ret = mp_montgomery_setup(m, &mp); + if (ret == MP_OKAY) + ret = mp_montgomery_calc_normalization(n, m); + /* a = 5: non-zero and coprime to the prime modulus. */ + if (ret == MP_OKAY) + ret = mp_set(a, 5); + /* n = a in Montgomery form (a * R mod m). */ + if (ret == MP_OKAY) + ret = mp_mulmod(a, n, m, n); + /* r = a^-1 in Montgomery form. */ + if (ret == MP_OKAY) + ret = mp_invmod_mont_ct(n, m, r, mp); + /* Take r out of Montgomery form: r = a^-1 mod m. */ + if (ret == MP_OKAY) + ret = mp_montgomery_reduce(r, m, mp); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + /* Verify a * a^-1 mod m == 1. */ + ret = mp_mulmod(a, r, m, n); + if (ret != MP_OKAY) + return WC_TEST_RET_ENC_EC(ret); + if (!mp_isone(n)) + return WC_TEST_RET_ENC_NC; +#endif /* WOLFSSL_SP_INVMOD_MONT_CT */ + return 0; } #endif @@ -70237,7 +70829,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t mp_test(void) goto done; #if !(defined(WOLFSSL_SP_MATH_ALL) || defined(WOLFSSL_SP_MATH)) || \ (defined(WOLFSSL_SP_ADD_D) && defined(WOLFSSL_SP_SUB_D)) - if ((ret = mp_test_add_sub_d(a, r1)) != 0) + if ((ret = mp_test_add_sub_d(a, b, r1)) != 0) goto done; #endif if ((ret = mp_test_read_to_bin(a)) != 0) @@ -70267,7 +70859,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t mp_test(void) if ((ret = mp_test_prime(a, &rng)) != 0) goto done; #endif -#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) && !defined(WC_RSA_BLINDING) +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) if ((ret = mp_test_lcm_gcd(a, b, r1, r2, &rng)) != 0) goto done; #endif @@ -70291,7 +70883,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t mp_test(void) #endif #if !defined(NO_RSA) || !defined(NO_DSA) || !defined(NO_DH) || \ (defined(HAVE_ECC) && defined(HAVE_COMP_KEY)) || defined(OPENSSL_EXTRA) - if ((ret = mp_test_exptmod(a, b, r1, r2)) != 0) + if ((ret = mp_test_exptmod(a, b, r1, r2, p)) != 0) goto done; #endif #if defined(HAVE_ECC) || \ @@ -70299,6 +70891,23 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t mp_test(void) if ((ret = mp_test_mont(a, b, r1, r2, &rng)) != 0) goto done; #endif +#if defined(WOLFSSL_SP_MATH_ALL) && defined(HAVE_ECC) + if ((ret = mp_test_addsubmod_ct(a, b, r1, r2, p, &rng)) != 0) + goto done; +#endif +#if defined(WOLFSSL_SP_MATH_ALL) && !defined(WOLFSSL_RSA_PUBLIC_ONLY) + if ((ret = mp_test_cmp_mag(a, b)) != 0) + goto done; +#endif +#if defined(WOLFSSL_SP_MATH_ALL) && !defined(WOLFSSL_RSA_VERIFY_ONLY) && \ + !defined(WOLFSSL_RSA_PUBLIC_ONLY) + if ((ret = mp_test_mulmod_sqrmod(a, b, p, r1)) != 0) + goto done; + if ((ret = mp_test_exch(a, b, r1, r2)) != 0) + goto done; + if ((ret = mp_test_to_unsigned_bin_len_ct(a)) != 0) + goto done; +#endif #ifdef MP_TEST_COND_SWAP_AVAILABLE if ((ret = mp_test_cond_swap(a, b)) != 0) goto done;