From 07a057756c63e6b550ad15bdda299dca3fdd962d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Wed, 9 Jul 2025 10:42:28 +0200 Subject: [PATCH 01/91] bignum_core: Add mbedtls_mpi_core_gcd_modinv_odd() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a direct translation of sict_mi2() from https://github.com/mpg/cryptohack/blob/main/ct-pres.py which was presented in the book club's special session. This commit only includes two test cases which is very little. Most of the test cases will be generated by Python modules that belong to the framework. However we can't have the framework generate those before we have the corresponding test function in the consuming branches. So, extended tests are coming as a 2nd step, after the test function has been merged. (The test cases in .misc should stay, as they can be convenient when working on the test function.) Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum_core.c | 185 ++++++++++++++++++ library/bignum_core.h | 34 ++++ tests/suites/test_suite_bignum_core.function | 91 +++++++++ tests/suites/test_suite_bignum_core.misc.data | 6 + 4 files changed, 316 insertions(+) diff --git a/library/bignum_core.c b/library/bignum_core.c index 88582c2d38..1e80a5bf5d 100644 --- a/library/bignum_core.c +++ b/library/bignum_core.c @@ -1019,4 +1019,189 @@ void mbedtls_mpi_core_from_mont_rep(mbedtls_mpi_uint *X, mbedtls_mpi_core_montmul(X, A, &Rinv, 1, N, AN_limbs, mm, T); } +/* + * Compute X = A - B mod N. + * Both A and B must be in [0, N) and so will the output. + */ +static void mpi_core_sub_mod(mbedtls_mpi_uint *X, + const mbedtls_mpi_uint *A, + const mbedtls_mpi_uint *B, + const mbedtls_mpi_uint *N, + size_t limbs) +{ + mbedtls_mpi_uint c = mbedtls_mpi_core_sub(X, A, B, limbs); + (void) mbedtls_mpi_core_add_if(X, N, limbs, (unsigned) c); +} + +/* + * Divide X by 2 mod N in place, assuming N is odd. + * The input must be in [0, N) and so will the output. + */ +static void mpi_core_div2_mod_odd(mbedtls_mpi_uint *X, + const mbedtls_mpi_uint *N, + size_t limbs) +{ + /* If X is odd, add N to make it even before shifting. */ + unsigned odd = (unsigned) X[0] & 1; + mbedtls_mpi_uint c = mbedtls_mpi_core_add_if(X, N, limbs, odd); + mbedtls_mpi_core_shift_r(X, limbs, 1); + X[limbs - 1] |= c << (biL - 1); +} + +/* + * Constant-time GCD and modular inversion - odd modulus. + * + * Pre-conditions: see public documentation. + * + * See https://www.jstage.jst.go.jp/article/transinf/E106.D/9/E106.D_2022ICP0009/_pdf + * This is an adaptation of Alg 7 / Alg 8: + * - Alg 7 is readable but not constant-time, Alg 8 is constant-time but not + * readable (and uses signed arithmetic). We mostly follow Alg 7 and make it + * constant-time by using our usual primitives (conditional assign, + * conditional swap) rather than re-inventing them. We only take a few + * notations from Alg 8 for temporaries. + * - Compared to both, we skip the trick with pre_comm: I think this trick + * complicates things for no benefit (see comment on the big I != NULL block + * below for details). + */ +void mbedtls_mpi_core_gcd_modinv_odd(mbedtls_mpi_uint *G, + mbedtls_mpi_uint *I, + const mbedtls_mpi_uint *A, + size_t A_limbs, + const mbedtls_mpi_uint *N, + size_t N_limbs, + mbedtls_mpi_uint *T) +{ + /* Note: N is called p in the paper, but doesn't need to be prime, only odd. + */ + + /* GCD and modinv, names common to Alg 7 and Alg 8 */ + mbedtls_mpi_uint *u = T + 0 * N_limbs; + mbedtls_mpi_uint *v = G; + + /* GCD and modinv, my name (t1, t2 from Alg 7) */ + mbedtls_mpi_uint *d = T + 1 * N_limbs; + + /* GCD and modinv, names from Alg 8 (note: t1, t2 from Alg 7 are d above) */ + mbedtls_mpi_uint *t1 = T + 2 * N_limbs; + mbedtls_mpi_uint *t2 = T + 3 * N_limbs; + + /* modinv only, names common to Alg 7 and Alg 8 */ + mbedtls_mpi_uint *q = I; + mbedtls_mpi_uint *r = I != NULL ? T + 4 * N_limbs : NULL; + + /* + * Initial values: + * u, v = A, N + * q, r = 0, 1 + */ + memcpy(u, A, A_limbs * ciL); + memset((char *) u + A_limbs * ciL, 0, (N_limbs - A_limbs) * ciL); + + memcpy(v, N, N_limbs * ciL); + + if (I != NULL) { + memset(q, 0, N_limbs * ciL); + + memset(r, 0, N_limbs * ciL); + r[0] = 1; + } + + /* + * At each step, out of u, v, v - u we keep one, shift another, and discard + * the third, then update (u, v) with the ordered result. + * Then we mirror those actions with q, r, r - q mod N. + * + * Loop invariants: + * u <= v (on entry: A <= N) + * GCD(u, v) == GCD(A, N) (on entry: trivial) + * v = A * q mod N (on entry: N = A * 0 mod N) + * u = A * r mod N (on entry: A = A * 1 mod N) + * q, r in [0, N) (on entry: 0, 1) + * + * On exit: + * u = 0 + * v = GCD(A, N) = A * q mod N + * if v == 1 then 1 = A * q mod N ie q is A's inverse mod N + * r = 0 + * + * The exit state is a fixed point of the loop's body. + * Alg 7 and Alg 8 use 2 * bitlen(N) iterations but Theorem 2 (above in the + * paper) says bitlen(A) + bitlen(N) is actually enough. + */ + for (size_t i = 0; i < (A_limbs + N_limbs) * biL; i++) { + /* s, z in Alg 8 - use meaningful names instead */ + mbedtls_ct_condition_t u_odd = mbedtls_ct_bool(u[0] & 1); + mbedtls_ct_condition_t v_odd = mbedtls_ct_bool(v[0] & 1); + + /* Other conditions that will be useful below */ + mbedtls_ct_condition_t u_odd_v_odd = mbedtls_ct_bool_and(u_odd, v_odd); + mbedtls_ct_condition_t v_even = mbedtls_ct_bool_not(v_odd); + mbedtls_ct_condition_t u_odd_v_even = mbedtls_ct_bool_and(u_odd, v_even); + + /* This is called t1 in Alg 7 (no name in Alg 8). + * We know that u <= v so there is no carry */ + (void) mbedtls_mpi_core_sub(d, v, u, N_limbs); + + /* t1 (the thing that's kept) can be d (default) or u (if t2 is d) */ + memcpy(t1, d, N_limbs * ciL); + mbedtls_mpi_core_cond_assign(t1, u, N_limbs, u_odd_v_odd); + + /* t2 (the thing that's shifted) can be u (if even), or v (if even), + * or d (which is even if both u and v were odd) */ + memcpy(t2, u, N_limbs * ciL); + mbedtls_mpi_core_cond_assign(t2, v, N_limbs, u_odd_v_even); + mbedtls_mpi_core_cond_assign(t2, d, N_limbs, u_odd_v_odd); + + mbedtls_mpi_core_shift_r(t2, N_limbs, 1); // t2 is even + + /* Update u, v and re-order them if needed */ + memcpy(u, t1, N_limbs * ciL); + memcpy(v, t2, N_limbs * ciL); + mbedtls_ct_condition_t swap = mbedtls_mpi_core_lt_ct(v, u, N_limbs); + mbedtls_mpi_core_cond_swap(u, v, N_limbs, swap); + + /* Now, if modinv was requested, do the same with q, r, but: + * - decisions still based on u and v (their initial values); + * - operations are now mod N; + * - we re-use t1, t2 for what the paper calls t3, t4 in Alg 8. + * + * Here we slightly diverge from the paper and instead do the obvious + * thing that preserves the invariants involving q and r: mirror + * operations on u and v, ie also divide by 2 here (mod N). + * + * The paper uses a trick where it replaces division by 2 with + * multiplication by 2 here, and compensates in the end by doing a + * final multiplication, which is probably intended as an optimisation. + * + * However I believe it's not actually an optimisation, since + * constant-time modular multiplication by 2 (left-shift + conditional + * subtract) is just as costly as constant-time modular division by 2 + * (conditional add + right-shift). So, skip it and keep things simple. + */ + if (I != NULL) { + /* This is called t2 in Alg 7 (no name in Alg 8). */ + mpi_core_sub_mod(d, q, r, N, N_limbs); + + /* t3 (the thing that's kept) */ + memcpy(t1, d, N_limbs * ciL); + mbedtls_mpi_core_cond_assign(t1, r, N_limbs, u_odd_v_odd); + + /* t4 (the thing that's shifted) */ + memcpy(t2, r, N_limbs * ciL); + mbedtls_mpi_core_cond_assign(t2, q, N_limbs, u_odd_v_even); + mbedtls_mpi_core_cond_assign(t2, d, N_limbs, u_odd_v_odd); + + mpi_core_div2_mod_odd(t2, N, N_limbs); + + /* Update and possibly swap */ + memcpy(r, t1, N_limbs * ciL); + memcpy(q, t2, N_limbs * ciL); + mbedtls_mpi_core_cond_swap(r, q, N_limbs, swap); + } + } + + /* G and I already hold the correct values by virtue of being aliased */ +} + #endif /* MBEDTLS_BIGNUM_C */ diff --git a/library/bignum_core.h b/library/bignum_core.h index 264ee63550..29e05cd3c9 100644 --- a/library/bignum_core.h +++ b/library/bignum_core.h @@ -822,4 +822,38 @@ void mbedtls_mpi_core_from_mont_rep(mbedtls_mpi_uint *X, mbedtls_mpi_uint mm, mbedtls_mpi_uint *T); +/** Compute GCD(A, N) and optionally the inverse of A mod N if it exists. + * + * Requires N to be odd, and 0 <= A <= N. + * + * None of the parameters may alias or overlap another. + * + * \param[out] G The GCD of \p A and \p N. + * Must have the same number of limbs as \p N. + * \param[out] I The inverse of \p A modulo \p N if it exists (that is, + * if \p G above is 1 on exit); indeterminate otherwise. + * This must either be NULL (to only compute the GCD), + * or have the same number of limbs as \p N. + * \param[in] A The 1st operand of GCD and number to invert. + * This value must be less than or equal to \p N. + * \param A_limbs The number of limbs of \p A. + * Must be less than or equal to \p N_limbs. + * \param[in] N The 2nd operand of GCD and modulus for inversion. + * Must be odd or the results are indeterminate. + * \param N_limbs The number of limbs of \p N. + * \param[in,out] T Temporary storage of size at least 5 * N_limbs limbs, + * or 4 * N_limbs if \p I is NULL (GCD only). + * Its initial content is unused and + * its final content is indeterminate. + * It must not alias or otherwise overlap any of the + * other parameters. + */ +void mbedtls_mpi_core_gcd_modinv_odd(mbedtls_mpi_uint *G, + mbedtls_mpi_uint *I, + const mbedtls_mpi_uint *A, + size_t A_limbs, + const mbedtls_mpi_uint *N, + size_t N_limbs, + mbedtls_mpi_uint *T); + #endif /* MBEDTLS_BIGNUM_CORE_H */ diff --git a/tests/suites/test_suite_bignum_core.function b/tests/suites/test_suite_bignum_core.function index c2b44bccdd..81c2242997 100644 --- a/tests/suites/test_suite_bignum_core.function +++ b/tests/suites/test_suite_bignum_core.function @@ -1356,3 +1356,94 @@ exit: mbedtls_free(X); } /* END_CASE */ + +/* BEGIN_CASE */ +void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, + char *input_exp_G, char *input_exp_I) +{ + mbedtls_mpi_uint *A = NULL; + size_t A_limbs = 0; + mbedtls_mpi_uint *N = NULL; + size_t N_limbs = 0; + mbedtls_mpi_uint *exp_G = NULL; + size_t exp_G_limbs = 0; + mbedtls_mpi_uint *exp_I = NULL; + size_t exp_I_limbs = 0; + mbedtls_mpi_uint *G = NULL, *I = NULL, *T = NULL; + + /* Read test parameters into MPI structures */ + TEST_EQUAL(0, mbedtls_test_read_mpi_core(&A, &A_limbs, input_A)); + TEST_EQUAL(0, mbedtls_test_read_mpi_core(&N, &N_limbs, input_N)); + TEST_EQUAL(0, mbedtls_test_read_mpi_core(&exp_G, &exp_G_limbs, input_exp_G)); + const unsigned char got_I = strlen(input_exp_I) != 0; + if (got_I) { + TEST_EQUAL(0, mbedtls_test_read_mpi_core(&exp_I, &exp_I_limbs, input_exp_I)); + } + + /* The function under test wants this */ + TEST_EQUAL(N[0] & 1, 1); + TEST_LE_U(A_limbs, N_limbs); + if (A_limbs == N_limbs) { + TEST_EQUAL(mbedtls_mpi_core_lt_ct(N, A, N_limbs), MBEDTLS_CT_FALSE); + } + /* Other things we want from test data, for our convenience */ + TEST_EQUAL(exp_G_limbs, N_limbs); + if (got_I) { + TEST_EQUAL(exp_I_limbs, N_limbs); + } + + const size_t limbs = N_limbs; + const size_t bytes = limbs * sizeof(mbedtls_mpi_uint); + + TEST_CF_SECRET(A, A_limbs * sizeof(mbedtls_mpi_uint)); + TEST_CF_SECRET(N, N_limbs * sizeof(mbedtls_mpi_uint)); + + /* + * Test GCD only (I == NULL) + */ + TEST_CALLOC(G, N_limbs); + memset(G, 'G', bytes); + + TEST_CALLOC(T, 4 * N_limbs); + memset(T, 'T', bytes); + + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, A, A_limbs, N, N_limbs, T); + TEST_CF_PUBLIC(G, bytes); + TEST_MEMORY_COMPARE(G, bytes, exp_G, bytes); + + mbedtls_free(G); + G = NULL; + mbedtls_free(T); + T = NULL; + + /* + * Test GCD + modinv + */ + TEST_CALLOC(G, N_limbs); + memset(G, 'G', bytes); + + TEST_CALLOC(I, N_limbs); + memset(I, 'I', bytes); + + TEST_CALLOC(T, 5 * N_limbs); + memset(T, 'T', bytes); + + mbedtls_mpi_core_gcd_modinv_odd(G, I, A, A_limbs, N, N_limbs, T); + + TEST_CF_PUBLIC(G, bytes); + TEST_MEMORY_COMPARE(G, bytes, exp_G, bytes); + if (got_I) { + TEST_CF_PUBLIC(I, bytes); + TEST_MEMORY_COMPARE(I, bytes, exp_I, bytes); + } + +exit: + mbedtls_free(A); + mbedtls_free(N); + mbedtls_free(exp_G); + mbedtls_free(exp_I); + mbedtls_free(G); + mbedtls_free(I); + mbedtls_free(T); +} +/* END_CASE */ diff --git a/tests/suites/test_suite_bignum_core.misc.data b/tests/suites/test_suite_bignum_core.misc.data index ba86029977..118ca07e65 100644 --- a/tests/suites/test_suite_bignum_core.misc.data +++ b/tests/suites/test_suite_bignum_core.misc.data @@ -523,3 +523,9 @@ mpi_core_clz:64:0 CLZ: 100000 0: skip overly long input mpi_core_clz:100000:0 + +GCD-modinv random 80-bit, non-trivial GCD -> no inverse +mpi_core_gcd_modinv_odd:"e4518a1900fce698fa3":"1a84113636607520200d":"00000000000000000003":"" + +GCD-modinv random 80-bit, trivial GCD -> inverse +mpi_core_gcd_modinv_odd:"7f2405d6de7db80a7bc":"1a84113636607520200d":"00000000000000000001":"15f158844a59cd7a3ed2" From de5eeb5ce9aa3efd4b0bdd5e003736fd0ba5cdf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 15 Jul 2025 12:12:36 +0200 Subject: [PATCH 02/91] Relax and test aliasing rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is consistent with the general rules documented at the top of the file: - when computing GCD(A, N), there is no modular arithmetic, so the output can alias any of the inputs; - when computing a modular inverse, N is the modulus, so it can't be aliased by any of the outputs (we'll use it for modular operations over the entire course of the function's execution). But since this function has two modes of operations with different aliasing rules (G can alias N only if I == NULL), I think it should really be stated explicitly. Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum_core.c | 10 ++- library/bignum_core.h | 4 +- tests/suites/test_suite_bignum_core.function | 91 ++++++++++++++++++++ 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/library/bignum_core.c b/library/bignum_core.c index 1e80a5bf5d..e020da100e 100644 --- a/library/bignum_core.c +++ b/library/bignum_core.c @@ -1094,11 +1094,19 @@ void mbedtls_mpi_core_gcd_modinv_odd(mbedtls_mpi_uint *G, * Initial values: * u, v = A, N * q, r = 0, 1 + * + * We only write to G (aka v) after reading from inputs (A and N), which + * allows aliasing, except with N when I != NULL, as then we'll be operating + * mod N on q and r later - see the public documentation. + * + * Also avoid possible UB with memcpy when src == dst. */ memcpy(u, A, A_limbs * ciL); memset((char *) u + A_limbs * ciL, 0, (N_limbs - A_limbs) * ciL); - memcpy(v, N, N_limbs * ciL); + if (v != N) { + memcpy(v, N, N_limbs * ciL); + } if (I != NULL) { memset(q, 0, N_limbs * ciL); diff --git a/library/bignum_core.h b/library/bignum_core.h index 29e05cd3c9..1589c34640 100644 --- a/library/bignum_core.h +++ b/library/bignum_core.h @@ -826,7 +826,9 @@ void mbedtls_mpi_core_from_mont_rep(mbedtls_mpi_uint *X, * * Requires N to be odd, and 0 <= A <= N. * - * None of the parameters may alias or overlap another. + * When I == NULL (computing only the GCD), G may alias A or N. + * When I != NULL (computing the modular inverse), G or I may alias A + * but none of them may alias N (the modulus). * * \param[out] G The GCD of \p A and \p N. * Must have the same number of limbs as \p N. diff --git a/tests/suites/test_suite_bignum_core.function b/tests/suites/test_suite_bignum_core.function index 81c2242997..93782c1331 100644 --- a/tests/suites/test_suite_bignum_core.function +++ b/tests/suites/test_suite_bignum_core.function @@ -1416,6 +1416,38 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, mbedtls_free(T); T = NULL; + /* GCD only, G aliased to N */ + TEST_CALLOC(G, N_limbs); + memcpy(G, N, bytes); + + TEST_CALLOC(T, 4 * N_limbs); + memset(T, 'T', bytes); + + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, A, A_limbs, G, N_limbs, T); + TEST_CF_PUBLIC(G, bytes); + TEST_MEMORY_COMPARE(G, bytes, exp_G, bytes); + + mbedtls_free(G); + G = NULL; + mbedtls_free(T); + T = NULL; + + /* GCD only, G aliased to A (hence A_limbs = N_limbs) */ + TEST_CALLOC(G, N_limbs); + memcpy(G, A, A_limbs * sizeof(mbedtls_mpi_uint)); + + TEST_CALLOC(T, 4 * N_limbs); + memset(T, 'T', bytes); + + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, G, N_limbs, N, N_limbs, T); + TEST_CF_PUBLIC(G, bytes); + TEST_MEMORY_COMPARE(G, bytes, exp_G, bytes); + + mbedtls_free(G); + G = NULL; + mbedtls_free(T); + T = NULL; + /* * Test GCD + modinv */ @@ -1437,6 +1469,65 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, TEST_MEMORY_COMPARE(I, bytes, exp_I, bytes); } + mbedtls_free(G); + G = NULL; + mbedtls_free(I); + I = NULL; + mbedtls_free(T); + T = NULL; + + /* GCD + modinv, G aliased to A */ + TEST_CALLOC(G, N_limbs); + memcpy(G, A, A_limbs * sizeof(mbedtls_mpi_uint)); + + TEST_CALLOC(I, N_limbs); + memset(I, 'I', bytes); + + TEST_CALLOC(T, 5 * N_limbs); + memset(T, 'T', bytes); + + mbedtls_mpi_core_gcd_modinv_odd(G, I, G, N_limbs, N, N_limbs, T); + + TEST_CF_PUBLIC(G, bytes); + TEST_MEMORY_COMPARE(G, bytes, exp_G, bytes); + if (got_I) { + TEST_CF_PUBLIC(I, bytes); + TEST_MEMORY_COMPARE(I, bytes, exp_I, bytes); + } + + mbedtls_free(G); + G = NULL; + mbedtls_free(I); + I = NULL; + mbedtls_free(T); + T = NULL; + + /* GCD + modinv, I aliased to A */ + TEST_CALLOC(G, N_limbs); + memset(G, 'G', bytes); + + TEST_CALLOC(I, N_limbs); + memcpy(I, A, A_limbs * sizeof(mbedtls_mpi_uint)); + + TEST_CALLOC(T, 5 * N_limbs); + memset(T, 'T', bytes); + + mbedtls_mpi_core_gcd_modinv_odd(G, I, I, N_limbs, N, N_limbs, T); + + TEST_CF_PUBLIC(G, bytes); + TEST_MEMORY_COMPARE(G, bytes, exp_G, bytes); + if (got_I) { + TEST_CF_PUBLIC(I, bytes); + TEST_MEMORY_COMPARE(I, bytes, exp_I, bytes); + } + + mbedtls_free(G); + G = NULL; + mbedtls_free(I); + I = NULL; + mbedtls_free(T); + T = NULL; + exit: mbedtls_free(A); mbedtls_free(N); From d0527406c04a10d8fbd95bd4ffeaf86562b3c1af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Wed, 16 Jul 2025 10:41:48 +0200 Subject: [PATCH 03/91] Relax number-of-limbs requirement on test data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also more precisely enforce requirement that A <= N. Signed-off-by: Manuel Pégourié-Gonnard --- tests/suites/test_suite_bignum_core.function | 47 ++++++++++++------- tests/suites/test_suite_bignum_core.misc.data | 4 +- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/tests/suites/test_suite_bignum_core.function b/tests/suites/test_suite_bignum_core.function index 93782c1331..1a67192908 100644 --- a/tests/suites/test_suite_bignum_core.function +++ b/tests/suites/test_suite_bignum_core.function @@ -148,6 +148,26 @@ exit: return ret; } +/* + * Return -1 if A < B, +1 if A > B and 0 if A == B + */ +static int mpi_core_cmp(const mbedtls_mpi_uint *A, size_t A_limbs, + const mbedtls_mpi_uint *B, size_t B_limbs) +{ + const mbedtls_mpi AA = { + .p = (mbedtls_mpi_uint *) A, + .s = 1, + .n = (unsigned short) A_limbs, + }; + const mbedtls_mpi BB = { + .p = (mbedtls_mpi_uint *) B, + .s = 1, + .n = (unsigned short) B_limbs, + }; + + return mbedtls_mpi_cmp_mpi(&AA, &BB); +} + /* END_HEADER */ /* BEGIN_DEPENDENCIES @@ -1383,14 +1403,7 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, /* The function under test wants this */ TEST_EQUAL(N[0] & 1, 1); TEST_LE_U(A_limbs, N_limbs); - if (A_limbs == N_limbs) { - TEST_EQUAL(mbedtls_mpi_core_lt_ct(N, A, N_limbs), MBEDTLS_CT_FALSE); - } - /* Other things we want from test data, for our convenience */ - TEST_EQUAL(exp_G_limbs, N_limbs); - if (got_I) { - TEST_EQUAL(exp_I_limbs, N_limbs); - } + TEST_ASSERT(mpi_core_cmp(A, A_limbs, N, N_limbs) <= 0); const size_t limbs = N_limbs; const size_t bytes = limbs * sizeof(mbedtls_mpi_uint); @@ -1409,7 +1422,7 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, mbedtls_mpi_core_gcd_modinv_odd(G, NULL, A, A_limbs, N, N_limbs, T); TEST_CF_PUBLIC(G, bytes); - TEST_MEMORY_COMPARE(G, bytes, exp_G, bytes); + TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); mbedtls_free(G); G = NULL; @@ -1425,7 +1438,7 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, mbedtls_mpi_core_gcd_modinv_odd(G, NULL, A, A_limbs, G, N_limbs, T); TEST_CF_PUBLIC(G, bytes); - TEST_MEMORY_COMPARE(G, bytes, exp_G, bytes); + TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); mbedtls_free(G); G = NULL; @@ -1441,7 +1454,7 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, mbedtls_mpi_core_gcd_modinv_odd(G, NULL, G, N_limbs, N, N_limbs, T); TEST_CF_PUBLIC(G, bytes); - TEST_MEMORY_COMPARE(G, bytes, exp_G, bytes); + TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); mbedtls_free(G); G = NULL; @@ -1463,10 +1476,10 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, mbedtls_mpi_core_gcd_modinv_odd(G, I, A, A_limbs, N, N_limbs, T); TEST_CF_PUBLIC(G, bytes); - TEST_MEMORY_COMPARE(G, bytes, exp_G, bytes); + TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); if (got_I) { TEST_CF_PUBLIC(I, bytes); - TEST_MEMORY_COMPARE(I, bytes, exp_I, bytes); + TEST_EQUAL(mpi_core_cmp(I, N_limbs, exp_I, exp_I_limbs), 0); } mbedtls_free(G); @@ -1489,10 +1502,10 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, mbedtls_mpi_core_gcd_modinv_odd(G, I, G, N_limbs, N, N_limbs, T); TEST_CF_PUBLIC(G, bytes); - TEST_MEMORY_COMPARE(G, bytes, exp_G, bytes); + TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); if (got_I) { TEST_CF_PUBLIC(I, bytes); - TEST_MEMORY_COMPARE(I, bytes, exp_I, bytes); + TEST_EQUAL(mpi_core_cmp(I, N_limbs, exp_I, exp_I_limbs), 0); } mbedtls_free(G); @@ -1515,10 +1528,10 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, mbedtls_mpi_core_gcd_modinv_odd(G, I, I, N_limbs, N, N_limbs, T); TEST_CF_PUBLIC(G, bytes); - TEST_MEMORY_COMPARE(G, bytes, exp_G, bytes); + TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); if (got_I) { TEST_CF_PUBLIC(I, bytes); - TEST_MEMORY_COMPARE(I, bytes, exp_I, bytes); + TEST_EQUAL(mpi_core_cmp(I, N_limbs, exp_I, exp_I_limbs), 0); } mbedtls_free(G); diff --git a/tests/suites/test_suite_bignum_core.misc.data b/tests/suites/test_suite_bignum_core.misc.data index 118ca07e65..67164e2f90 100644 --- a/tests/suites/test_suite_bignum_core.misc.data +++ b/tests/suites/test_suite_bignum_core.misc.data @@ -525,7 +525,7 @@ CLZ: 100000 0: skip overly long input mpi_core_clz:100000:0 GCD-modinv random 80-bit, non-trivial GCD -> no inverse -mpi_core_gcd_modinv_odd:"e4518a1900fce698fa3":"1a84113636607520200d":"00000000000000000003":"" +mpi_core_gcd_modinv_odd:"e4518a1900fce698fa3":"1a84113636607520200d":"3":"" GCD-modinv random 80-bit, trivial GCD -> inverse -mpi_core_gcd_modinv_odd:"7f2405d6de7db80a7bc":"1a84113636607520200d":"00000000000000000001":"15f158844a59cd7a3ed2" +mpi_core_gcd_modinv_odd:"7f2405d6de7db80a7bc":"1a84113636607520200d":"1":"15f158844a59cd7a3ed2" From 04ac5d8d35b3dd5753d816f2f4504e0f92d043a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Wed, 16 Jul 2025 10:55:24 +0200 Subject: [PATCH 04/91] Reduce clutter & improve readbility in test func MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- tests/suites/test_suite_bignum_core.function | 65 +++++++------------- 1 file changed, 22 insertions(+), 43 deletions(-) diff --git a/tests/suites/test_suite_bignum_core.function b/tests/suites/test_suite_bignum_core.function index 1a67192908..b82ba19e33 100644 --- a/tests/suites/test_suite_bignum_core.function +++ b/tests/suites/test_suite_bignum_core.function @@ -154,6 +154,9 @@ exit: static int mpi_core_cmp(const mbedtls_mpi_uint *A, size_t A_limbs, const mbedtls_mpi_uint *B, size_t B_limbs) { + TEST_CF_PUBLIC(A, A_limbs * sizeof(mbedtls_mpi_uint)); + TEST_CF_PUBLIC(B, B_limbs * sizeof(mbedtls_mpi_uint)); + const mbedtls_mpi AA = { .p = (mbedtls_mpi_uint *) A, .s = 1, @@ -1411,6 +1414,14 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, TEST_CF_SECRET(A, A_limbs * sizeof(mbedtls_mpi_uint)); TEST_CF_SECRET(N, N_limbs * sizeof(mbedtls_mpi_uint)); +#define FREE_G_I_T \ + mbedtls_free(G); \ + G = NULL; \ + mbedtls_free(I); \ + I = NULL; \ + mbedtls_free(T); \ + T = NULL + /* * Test GCD only (I == NULL) */ @@ -1421,13 +1432,9 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, memset(T, 'T', bytes); mbedtls_mpi_core_gcd_modinv_odd(G, NULL, A, A_limbs, N, N_limbs, T); - TEST_CF_PUBLIC(G, bytes); TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); - mbedtls_free(G); - G = NULL; - mbedtls_free(T); - T = NULL; + FREE_G_I_T; /* GCD only, G aliased to N */ TEST_CALLOC(G, N_limbs); @@ -1436,14 +1443,10 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, TEST_CALLOC(T, 4 * N_limbs); memset(T, 'T', bytes); - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, A, A_limbs, G, N_limbs, T); - TEST_CF_PUBLIC(G, bytes); + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, A, A_limbs, /* N */ G, N_limbs, T); TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); - mbedtls_free(G); - G = NULL; - mbedtls_free(T); - T = NULL; + FREE_G_I_T; /* GCD only, G aliased to A (hence A_limbs = N_limbs) */ TEST_CALLOC(G, N_limbs); @@ -1452,14 +1455,10 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, TEST_CALLOC(T, 4 * N_limbs); memset(T, 'T', bytes); - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, G, N_limbs, N, N_limbs, T); - TEST_CF_PUBLIC(G, bytes); + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, /* A */ G, N_limbs, N, N_limbs, T); TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); - mbedtls_free(G); - G = NULL; - mbedtls_free(T); - T = NULL; + FREE_G_I_T; /* * Test GCD + modinv @@ -1475,19 +1474,12 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, mbedtls_mpi_core_gcd_modinv_odd(G, I, A, A_limbs, N, N_limbs, T); - TEST_CF_PUBLIC(G, bytes); TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); if (got_I) { - TEST_CF_PUBLIC(I, bytes); TEST_EQUAL(mpi_core_cmp(I, N_limbs, exp_I, exp_I_limbs), 0); } - mbedtls_free(G); - G = NULL; - mbedtls_free(I); - I = NULL; - mbedtls_free(T); - T = NULL; + FREE_G_I_T; /* GCD + modinv, G aliased to A */ TEST_CALLOC(G, N_limbs); @@ -1499,21 +1491,14 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, TEST_CALLOC(T, 5 * N_limbs); memset(T, 'T', bytes); - mbedtls_mpi_core_gcd_modinv_odd(G, I, G, N_limbs, N, N_limbs, T); + mbedtls_mpi_core_gcd_modinv_odd(G, I, /* A */ G, N_limbs, N, N_limbs, T); - TEST_CF_PUBLIC(G, bytes); TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); if (got_I) { - TEST_CF_PUBLIC(I, bytes); TEST_EQUAL(mpi_core_cmp(I, N_limbs, exp_I, exp_I_limbs), 0); } - mbedtls_free(G); - G = NULL; - mbedtls_free(I); - I = NULL; - mbedtls_free(T); - T = NULL; + FREE_G_I_T; /* GCD + modinv, I aliased to A */ TEST_CALLOC(G, N_limbs); @@ -1525,22 +1510,16 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, TEST_CALLOC(T, 5 * N_limbs); memset(T, 'T', bytes); - mbedtls_mpi_core_gcd_modinv_odd(G, I, I, N_limbs, N, N_limbs, T); + mbedtls_mpi_core_gcd_modinv_odd(G, I, /* A */ I, N_limbs, N, N_limbs, T); - TEST_CF_PUBLIC(G, bytes); TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); if (got_I) { - TEST_CF_PUBLIC(I, bytes); TEST_EQUAL(mpi_core_cmp(I, N_limbs, exp_I, exp_I_limbs), 0); } - mbedtls_free(G); - G = NULL; - mbedtls_free(I); - I = NULL; - mbedtls_free(T); - T = NULL; + FREE_G_I_T; +#undef FREE_G_I_T exit: mbedtls_free(A); mbedtls_free(N); From fb2001faf541e55bf370aae091f2274ff5fa82fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Wed, 16 Jul 2025 12:20:33 +0200 Subject: [PATCH 05/91] Make sure the whole temporary array is non-zero MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- tests/suites/test_suite_bignum_core.function | 27 ++++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/suites/test_suite_bignum_core.function b/tests/suites/test_suite_bignum_core.function index b82ba19e33..61660c4851 100644 --- a/tests/suites/test_suite_bignum_core.function +++ b/tests/suites/test_suite_bignum_core.function @@ -1408,8 +1408,7 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, TEST_LE_U(A_limbs, N_limbs); TEST_ASSERT(mpi_core_cmp(A, A_limbs, N, N_limbs) <= 0); - const size_t limbs = N_limbs; - const size_t bytes = limbs * sizeof(mbedtls_mpi_uint); + const size_t N_bytes = N_limbs * sizeof(mbedtls_mpi_uint); TEST_CF_SECRET(A, A_limbs * sizeof(mbedtls_mpi_uint)); TEST_CF_SECRET(N, N_limbs * sizeof(mbedtls_mpi_uint)); @@ -1426,10 +1425,10 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, * Test GCD only (I == NULL) */ TEST_CALLOC(G, N_limbs); - memset(G, 'G', bytes); + memset(G, 'G', N_bytes); TEST_CALLOC(T, 4 * N_limbs); - memset(T, 'T', bytes); + memset(T, 'T', 4 * N_bytes); mbedtls_mpi_core_gcd_modinv_odd(G, NULL, A, A_limbs, N, N_limbs, T); TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); @@ -1438,10 +1437,10 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, /* GCD only, G aliased to N */ TEST_CALLOC(G, N_limbs); - memcpy(G, N, bytes); + memcpy(G, N, N_bytes); TEST_CALLOC(T, 4 * N_limbs); - memset(T, 'T', bytes); + memset(T, 'T', 4 * N_bytes); mbedtls_mpi_core_gcd_modinv_odd(G, NULL, A, A_limbs, /* N */ G, N_limbs, T); TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); @@ -1453,7 +1452,7 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, memcpy(G, A, A_limbs * sizeof(mbedtls_mpi_uint)); TEST_CALLOC(T, 4 * N_limbs); - memset(T, 'T', bytes); + memset(T, 'T', 4 * N_bytes); mbedtls_mpi_core_gcd_modinv_odd(G, NULL, /* A */ G, N_limbs, N, N_limbs, T); TEST_EQUAL(mpi_core_cmp(G, N_limbs, exp_G, exp_G_limbs), 0); @@ -1464,13 +1463,13 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, * Test GCD + modinv */ TEST_CALLOC(G, N_limbs); - memset(G, 'G', bytes); + memset(G, 'G', N_bytes); TEST_CALLOC(I, N_limbs); - memset(I, 'I', bytes); + memset(I, 'I', N_bytes); TEST_CALLOC(T, 5 * N_limbs); - memset(T, 'T', bytes); + memset(T, 'T', 5 * N_bytes); mbedtls_mpi_core_gcd_modinv_odd(G, I, A, A_limbs, N, N_limbs, T); @@ -1486,10 +1485,10 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, memcpy(G, A, A_limbs * sizeof(mbedtls_mpi_uint)); TEST_CALLOC(I, N_limbs); - memset(I, 'I', bytes); + memset(I, 'I', N_bytes); TEST_CALLOC(T, 5 * N_limbs); - memset(T, 'T', bytes); + memset(T, 'T', 5 * N_bytes); mbedtls_mpi_core_gcd_modinv_odd(G, I, /* A */ G, N_limbs, N, N_limbs, T); @@ -1502,13 +1501,13 @@ void mpi_core_gcd_modinv_odd(char *input_A, char *input_N, /* GCD + modinv, I aliased to A */ TEST_CALLOC(G, N_limbs); - memset(G, 'G', bytes); + memset(G, 'G', N_bytes); TEST_CALLOC(I, N_limbs); memcpy(I, A, A_limbs * sizeof(mbedtls_mpi_uint)); TEST_CALLOC(T, 5 * N_limbs); - memset(T, 'T', bytes); + memset(T, 'T', 5 * N_bytes); mbedtls_mpi_core_gcd_modinv_odd(G, I, /* A */ I, N_limbs, N, N_limbs, T); From 7fba4668264bfbf8fd3bc8e4bfc7e48d7d6525c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 17 Jul 2025 09:17:12 +0200 Subject: [PATCH 06/91] Unit-test mpi_core_div2_mod_odd() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This function has specific code to handle carries and it's not clear how to exercises that code through the modinv function, so well, that's what unit tests are for. Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum_core.c | 10 +++--- library/bignum_core_invasive.h | 19 +++++++++-- tests/suites/test_suite_bignum_core.function | 34 +++++++++++++++++++ tests/suites/test_suite_bignum_core.misc.data | 12 +++++++ 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/library/bignum_core.c b/library/bignum_core.c index e020da100e..3490f7d885 100644 --- a/library/bignum_core.c +++ b/library/bignum_core.c @@ -18,6 +18,7 @@ #include "mbedtls/platform.h" #include "bignum_core.h" +#include "bignum_core_invasive.h" #include "bn_mul.h" #include "constant_time_internal.h" @@ -1037,9 +1038,10 @@ static void mpi_core_sub_mod(mbedtls_mpi_uint *X, * Divide X by 2 mod N in place, assuming N is odd. * The input must be in [0, N) and so will the output. */ -static void mpi_core_div2_mod_odd(mbedtls_mpi_uint *X, - const mbedtls_mpi_uint *N, - size_t limbs) +MBEDTLS_STATIC_TESTABLE +void mbedtls_mpi_core_div2_mod_odd(mbedtls_mpi_uint *X, + const mbedtls_mpi_uint *N, + size_t limbs) { /* If X is odd, add N to make it even before shifting. */ unsigned odd = (unsigned) X[0] & 1; @@ -1200,7 +1202,7 @@ void mbedtls_mpi_core_gcd_modinv_odd(mbedtls_mpi_uint *G, mbedtls_mpi_core_cond_assign(t2, q, N_limbs, u_odd_v_even); mbedtls_mpi_core_cond_assign(t2, d, N_limbs, u_odd_v_odd); - mpi_core_div2_mod_odd(t2, N, N_limbs); + mbedtls_mpi_core_div2_mod_odd(t2, N, N_limbs); /* Update and possibly swap */ memcpy(r, t1, N_limbs * ciL); diff --git a/library/bignum_core_invasive.h b/library/bignum_core_invasive.h index 167099dc91..a9d447f792 100644 --- a/library/bignum_core_invasive.h +++ b/library/bignum_core_invasive.h @@ -13,11 +13,26 @@ #include "bignum_core.h" -#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C) +#if defined(MBEDTLS_TEST_HOOKS) + +#if !defined(MBEDTLS_THREADING_C) extern void (*mbedtls_safe_codepath_hook)(void); extern void (*mbedtls_unsafe_codepath_hook)(void); -#endif /* MBEDTLS_TEST_HOOKS && !MBEDTLS_THREADING_C */ +#endif /* !MBEDTLS_THREADING_C */ + +/** Divide X by 2 mod N in place, assuming N is odd. + * + * \param[in,out] X The value to divide by 2 mod \p N. + * \param[in] N The modulus. Must be odd. + * \param[in] limbs The number of limbs in \p X and \p N. + */ +MBEDTLS_STATIC_TESTABLE +void mbedtls_mpi_core_div2_mod_odd(mbedtls_mpi_uint *X, + const mbedtls_mpi_uint *N, + size_t limbs); + +#endif /* MBEDTLS_TEST_HOOKS */ #endif /* MBEDTLS_BIGNUM_CORE_INVASIVE_H */ diff --git a/tests/suites/test_suite_bignum_core.function b/tests/suites/test_suite_bignum_core.function index 61660c4851..cad9c1c0dc 100644 --- a/tests/suites/test_suite_bignum_core.function +++ b/tests/suites/test_suite_bignum_core.function @@ -2,6 +2,7 @@ #include "mbedtls/bignum.h" #include "mbedtls/entropy.h" #include "bignum_core.h" +#include "bignum_core_invasive.h" #include "constant_time_internal.h" #include "test/constant_flow.h" #include "test/bignum_codepath_check.h" @@ -1529,3 +1530,36 @@ exit: mbedtls_free(T); } /* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_TEST_HOOKS */ +void mpi_core_div2_mod_odd(char *input_X, char *input_N, char *input_exp_X) +{ + mbedtls_mpi_uint *X = NULL; + size_t X_limbs = 0; + mbedtls_mpi_uint *N = NULL; + size_t N_limbs = 0; + mbedtls_mpi_uint *exp_X = NULL; + size_t exp_X_limbs = 0; + + /* Read test parameters into MPI structures */ + TEST_EQUAL(0, mbedtls_test_read_mpi_core(&X, &X_limbs, input_X)); + TEST_EQUAL(0, mbedtls_test_read_mpi_core(&N, &N_limbs, input_N)); + TEST_EQUAL(0, mbedtls_test_read_mpi_core(&exp_X, &exp_X_limbs, input_exp_X)); + + /* The function under test requires this */ + TEST_EQUAL(X_limbs, N_limbs); + + TEST_CF_SECRET(X, X_limbs * sizeof(mbedtls_mpi_uint)); + TEST_CF_SECRET(N, N_limbs * sizeof(mbedtls_mpi_uint)); + + mbedtls_mpi_core_div2_mod_odd(X, N, N_limbs); + + TEST_CF_PUBLIC(X, X_limbs * sizeof(mbedtls_mpi_uint)); + TEST_EQUAL(0, mpi_core_cmp(X, X_limbs, exp_X, exp_X_limbs)); + +exit: + mbedtls_free(X); + mbedtls_free(N); + mbedtls_free(exp_X); +} +/* END_CASE */ diff --git a/tests/suites/test_suite_bignum_core.misc.data b/tests/suites/test_suite_bignum_core.misc.data index 67164e2f90..d4cf00f893 100644 --- a/tests/suites/test_suite_bignum_core.misc.data +++ b/tests/suites/test_suite_bignum_core.misc.data @@ -529,3 +529,15 @@ mpi_core_gcd_modinv_odd:"e4518a1900fce698fa3":"1a84113636607520200d":"3":"" GCD-modinv random 80-bit, trivial GCD -> inverse mpi_core_gcd_modinv_odd:"7f2405d6de7db80a7bc":"1a84113636607520200d":"1":"15f158844a59cd7a3ed2" + +Div2 mod odd: even value +mpi_core_div2_mod_odd:"4":"7":"2" + +Div2 mod odd: odd value, no carry +mpi_core_div2_mod_odd:"5":"7":"6" + +Div2 mod odd: odd value with carry +mpi_core_div2_mod_odd:"8000000000000001":"8000000000000003":"8000000000000002" + +Div2 mod odd: even value with top bit set +mpi_core_div2_mod_odd:"8000000000000002":"8000000000000003":"4000000000000001" From 5972096114127759abce1c8450b29129cba97814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 17 Jul 2025 09:39:55 +0200 Subject: [PATCH 07/91] Forbid uninteresting edge cases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A == N (as pointers) will not happen in pratice: in our context, it would mean we know at compile time that A == N (as values), and we wouldn't be calling this function if we knew that already. N == 1 when I != NULL is also not going to happen: we don't care about operations mod 1. Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum_core.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/bignum_core.h b/library/bignum_core.h index 1589c34640..b8e0807170 100644 --- a/library/bignum_core.h +++ b/library/bignum_core.h @@ -825,7 +825,9 @@ void mbedtls_mpi_core_from_mont_rep(mbedtls_mpi_uint *X, /** Compute GCD(A, N) and optionally the inverse of A mod N if it exists. * * Requires N to be odd, and 0 <= A <= N. + * When I != NULL, N (the modulus) must not be 1. * + * A and N may not alias each other. * When I == NULL (computing only the GCD), G may alias A or N. * When I != NULL (computing the modular inverse), G or I may alias A * but none of them may alias N (the modulus). From 0d25cd965d89a5be0d5dd5aa68bce9a92d3a8c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 17 Jul 2025 10:10:56 +0200 Subject: [PATCH 08/91] Add test case exercising (almost) max iterations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With this data, the loop only settles to its final state u == 0 and v == GCD(A, N) at the last iteration. However it already has v == GCD(A, N) at the previous iteration. Concretely, this means that if in mbedtls_mpi_core_gcd_modinv_odd() we change the main loop as follows - for (size_t i = 0; i < (A_limbs + N_limbs) * biL; i++) { + for (size_t i = 0; i < (A_limbs + N_limbs) * biL - 2; i++) { then this test case would fail. Ideally we'd like a test case that would fail with -1 above but I've not been able to find one and I have no idea whether that's possible. Experimentally I've systematically tried small values (8 bit) and noticed the case A = 2^n and N significantly larger then A is promising, so I explored that further. Clearly we want A and N's bitlength to be a multiple of biL because the bound in the paper is with bitlenths while we use limbs * biL. Anyway, I ended up with the following Python script. import secrets import math bil = 64 def bitlimbs(x): return (x.bit_length() + bil - 1) // bil * bil def sict_gcd(p, a): assert p >= a >= 0 assert p & 1 != 0 or a & 1 != 0 u, v = a, p for i in range(2 * p.bit_length()): s, z = u & 1, v & 1 t1 = (s ^ z) * v + (2 * s * z - 1) * u t2 = (s * v + (2 - 2 * s - z) * u) >> 1 if t2 >= t1: u, v = t1, t2 else: u, v = t2, t1 if u == 0: # v == 1 ideally, but can't get it return bitlimbs(a) + bitlimbs(p) - (i + 1) return 0 a = 2 ** (bil - 1) m = 1000 while m != 0: n = secrets.randbits(2 * bil) | 1 d = sict_gcd(n, a) if d < m: m = d print(d) g = math.gcd(a, n) i = pow(a, -1, n) print(f'mpi_core_gcd_modinv_odd:"{a:x}":"{n:x}":"{g:x}":"{i:x}"') Signed-off-by: Manuel Pégourié-Gonnard --- tests/suites/test_suite_bignum_core.misc.data | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/suites/test_suite_bignum_core.misc.data b/tests/suites/test_suite_bignum_core.misc.data index d4cf00f893..5d3c6753ce 100644 --- a/tests/suites/test_suite_bignum_core.misc.data +++ b/tests/suites/test_suite_bignum_core.misc.data @@ -530,6 +530,11 @@ mpi_core_gcd_modinv_odd:"e4518a1900fce698fa3":"1a84113636607520200d":"3":"" GCD-modinv random 80-bit, trivial GCD -> inverse mpi_core_gcd_modinv_odd:"7f2405d6de7db80a7bc":"1a84113636607520200d":"1":"15f158844a59cd7a3ed2" +# This data results in the gcd-modinv loop converging to its final state +# only in the last iteration. See python script in commit message. +GCD-modinv (almost) max iterations +mpi_core_gcd_modinv_odd:"8000000000000000":"b26eb5721a2cb24c36acb4550b176671":"1":"77e1dd63583a6b3c8deefe7737862c89" + Div2 mod odd: even value mpi_core_div2_mod_odd:"4":"7":"2" From ed711e142080c18645da5cddbdb1a184d345a429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 22 Jul 2025 09:00:52 +0200 Subject: [PATCH 09/91] Clarify preconditions and impact if not met MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum_core.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/library/bignum_core.h b/library/bignum_core.h index b8e0807170..cd78e723f1 100644 --- a/library/bignum_core.h +++ b/library/bignum_core.h @@ -824,14 +824,16 @@ void mbedtls_mpi_core_from_mont_rep(mbedtls_mpi_uint *X, /** Compute GCD(A, N) and optionally the inverse of A mod N if it exists. * - * Requires N to be odd, and 0 <= A <= N. - * When I != NULL, N (the modulus) must not be 1. + * Requires N to be odd, 0 <= A <= N and A_limbs <= N_limbs. + * When I != NULL, N (the modulus) must be greater than 1. * * A and N may not alias each other. * When I == NULL (computing only the GCD), G may alias A or N. * When I != NULL (computing the modular inverse), G or I may alias A * but none of them may alias N (the modulus). * + * If any precondition is not met, output values are unspecified. + * * \param[out] G The GCD of \p A and \p N. * Must have the same number of limbs as \p N. * \param[out] I The inverse of \p A modulo \p N if it exists (that is, @@ -843,7 +845,8 @@ void mbedtls_mpi_core_from_mont_rep(mbedtls_mpi_uint *X, * \param A_limbs The number of limbs of \p A. * Must be less than or equal to \p N_limbs. * \param[in] N The 2nd operand of GCD and modulus for inversion. - * Must be odd or the results are indeterminate. + * This value must be odd. + * If I != NULL this value must be greater than 1. * \param N_limbs The number of limbs of \p N. * \param[in,out] T Temporary storage of size at least 5 * N_limbs limbs, * or 4 * N_limbs if \p I is NULL (GCD only). From dbda87236900e02be9173f79be38357f6d70b5ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 22 Jul 2025 09:21:53 +0200 Subject: [PATCH 10/91] Expand comment about adaptations from the paper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum_core.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/library/bignum_core.c b/library/bignum_core.c index 3490f7d885..2ec4a8fca5 100644 --- a/library/bignum_core.c +++ b/library/bignum_core.c @@ -1060,8 +1060,17 @@ void mbedtls_mpi_core_div2_mod_odd(mbedtls_mpi_uint *X, * - Alg 7 is readable but not constant-time, Alg 8 is constant-time but not * readable (and uses signed arithmetic). We mostly follow Alg 7 and make it * constant-time by using our usual primitives (conditional assign, - * conditional swap) rather than re-inventing them. We only take a few - * notations from Alg 8 for temporaries. + * conditional swap) rather than re-creating them. See the comments in the + * body of the paper (around tables 2) about how to make Alg 7 constant-time. + * - Both Alg 7 and Alg 8 have temporaries called t1, t2 which have different + * meanings; we use the meaning from Alg 8 (see declarations below). + * - Compared to both, we re-order operations, grouping those related to + * the inverse together. This saves temporaries (we can re-use d, t1, t2 from + * the GCD part as they are no longer used) and improves readability + * considering we make computation of the inverse optional. + * - Compared to Alg 7, we use an explicit conditional swap at the end, which is + * closer to the use of the sort array in Alg 8 (or the max.min function in + * Alg 6 and earlier). * - Compared to both, we skip the trick with pre_comm: I think this trick * complicates things for no benefit (see comment on the big I != NULL block * below for details). From 9361550c4599682a099f7b5256d3ed7ea7b42cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Wed, 23 Jul 2025 13:21:07 +0200 Subject: [PATCH 11/91] Tune comment about paper vs our code again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum_core.c | 56 +++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/library/bignum_core.c b/library/bignum_core.c index 2ec4a8fca5..49a966fb3f 100644 --- a/library/bignum_core.c +++ b/library/bignum_core.c @@ -1056,24 +1056,41 @@ void mbedtls_mpi_core_div2_mod_odd(mbedtls_mpi_uint *X, * Pre-conditions: see public documentation. * * See https://www.jstage.jst.go.jp/article/transinf/E106.D/9/E106.D_2022ICP0009/_pdf - * This is an adaptation of Alg 7 / Alg 8: - * - Alg 7 is readable but not constant-time, Alg 8 is constant-time but not - * readable (and uses signed arithmetic). We mostly follow Alg 7 and make it - * constant-time by using our usual primitives (conditional assign, - * conditional swap) rather than re-creating them. See the comments in the - * body of the paper (around tables 2) about how to make Alg 7 constant-time. - * - Both Alg 7 and Alg 8 have temporaries called t1, t2 which have different - * meanings; we use the meaning from Alg 8 (see declarations below). - * - Compared to both, we re-order operations, grouping those related to - * the inverse together. This saves temporaries (we can re-use d, t1, t2 from - * the GCD part as they are no longer used) and improves readability - * considering we make computation of the inverse optional. - * - Compared to Alg 7, we use an explicit conditional swap at the end, which is - * closer to the use of the sort array in Alg 8 (or the max.min function in - * Alg 6 and earlier). - * - Compared to both, we skip the trick with pre_comm: I think this trick - * complicates things for no benefit (see comment on the big I != NULL block - * below for details). + * This is a minor rewrite + adaptation from Algorithm 8: SICT-MI (p. 1403). + * + * u, v = A, N # N is called p in the paper but doesn't have to be prime + * q, r = 0, 1 + * repeat bits(A_limbs + N_limbs) times: + * d = v - u + * t1 = (u and v both odd) ? u : d + * t2 = (u and v both odd) ? d : (u odd) ? v : u + * t2 >>= 1 + * s = t1 <= t2 + * u, v = (s) ? t1, t2 : t2, t1 + * + * d = r - q mod N + * t1 = (u and v both odd) ? q : d # t3 in the paper + * t2 = (u and v both odd) ? d : (u odd) ? r : q # t4 in the paper + * t2 /= 2 mod N + * q, r = (s) ? t1, t2 : t2, t1 + * return v, q + * + * The ternary operators in the above pseudo-code need to be realised in a + * constant-time fashion. For t1-t4, The paper does it by using a mixture of bit + * and (signed) arithmetic operations which is hardly readable; we'll use + * conditional assign instead. We'll use conditional swap for the final update. + * + * Note: for insight about the complicated expressions for t1-t4 in Alg 8, it is + * useful to look at Alg 7 (warning: different meanings for t1, t2, they're our + * d above), table 2 and the surrounding text. + * + * The other minor rewrite compared to Alg 8 in the paper is we re-ordered + * operations, grouping things related to the inverse, which facilitates making + * its computation optional, and requires fewer temporaries. + * + * The only actual change from Alg 8 is dropping the trick with pre_comm, + * which I think complicates things for no benefit. + * See the comment on the big I != NULL block below for details. */ void mbedtls_mpi_core_gcd_modinv_odd(mbedtls_mpi_uint *G, mbedtls_mpi_uint *I, @@ -1083,9 +1100,6 @@ void mbedtls_mpi_core_gcd_modinv_odd(mbedtls_mpi_uint *G, size_t N_limbs, mbedtls_mpi_uint *T) { - /* Note: N is called p in the paper, but doesn't need to be prime, only odd. - */ - /* GCD and modinv, names common to Alg 7 and Alg 8 */ mbedtls_mpi_uint *u = T + 0 * N_limbs; mbedtls_mpi_uint *v = G; From efd242a0e5e79759a98e99c1959987f8b5fa890a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 24 Jul 2025 11:10:59 +0200 Subject: [PATCH 12/91] Gracefully handle A_limbs > N_limbs and test it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum_core.c | 7 +- library/bignum_core.h | 2 +- tests/suites/test_suite_bignum_core.function | 104 ++++++++++++++++++ tests/suites/test_suite_bignum_core.misc.data | 3 + 4 files changed, 113 insertions(+), 3 deletions(-) diff --git a/library/bignum_core.c b/library/bignum_core.c index 49a966fb3f..8ac65e2202 100644 --- a/library/bignum_core.c +++ b/library/bignum_core.c @@ -1123,12 +1123,15 @@ void mbedtls_mpi_core_gcd_modinv_odd(mbedtls_mpi_uint *G, * We only write to G (aka v) after reading from inputs (A and N), which * allows aliasing, except with N when I != NULL, as then we'll be operating * mod N on q and r later - see the public documentation. - * - * Also avoid possible UB with memcpy when src == dst. */ + if (A_limbs > N_limbs) { + /* Violating this precondition should not result in memory errors. */ + A_limbs = N_limbs; + } memcpy(u, A, A_limbs * ciL); memset((char *) u + A_limbs * ciL, 0, (N_limbs - A_limbs) * ciL); + /* Avoid possible UB with memcpy when src == dst. */ if (v != N) { memcpy(v, N, N_limbs * ciL); } diff --git a/library/bignum_core.h b/library/bignum_core.h index cd78e723f1..f044b33f93 100644 --- a/library/bignum_core.h +++ b/library/bignum_core.h @@ -832,7 +832,7 @@ void mbedtls_mpi_core_from_mont_rep(mbedtls_mpi_uint *X, * When I != NULL (computing the modular inverse), G or I may alias A * but none of them may alias N (the modulus). * - * If any precondition is not met, output values are unspecified. + * If any of the above preconditions is not met, output values are unspecified. * * \param[out] G The GCD of \p A and \p N. * Must have the same number of limbs as \p N. diff --git a/tests/suites/test_suite_bignum_core.function b/tests/suites/test_suite_bignum_core.function index cad9c1c0dc..8ae1931cbc 100644 --- a/tests/suites/test_suite_bignum_core.function +++ b/tests/suites/test_suite_bignum_core.function @@ -1531,6 +1531,110 @@ exit: } /* END_CASE */ +/* BEGIN_CASE */ +void mpi_core_gcd_modinv_odd_preconditions() +{ + /* + * The purpose of this test function is to ensure that the function doesn't + * crash (but just outputs garbage) when preconditions are not met. + */ + + mbedtls_mpi_uint two_limbs[2]; + mbedtls_mpi_uint one_limb[1]; + mbedtls_mpi_uint *G = NULL, *I = NULL, *T = NULL; + + /* Large enough for all calls below */ + TEST_CALLOC(G, 2); + TEST_CALLOC(I, 2); + TEST_CALLOC(T, 5 * 2); + + /* + * Input values + */ + + /* N is not odd */ + two_limbs[0] = 2; // N = 2^n + 2 + two_limbs[1] = 1; + one_limb[0] = 42; // A = 42 + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, one_limb, 1, two_limbs, 2, T); + mbedtls_mpi_core_gcd_modinv_odd(G, I, one_limb, 1, two_limbs, 2, T); + + /* A > N */ + two_limbs[0] = 3; // N = 3 + two_limbs[1] = 0; + one_limb[0] = 42; // A = 42 + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, one_limb, 1, two_limbs, 2, T); + mbedtls_mpi_core_gcd_modinv_odd(G, I, one_limb, 1, two_limbs, 2, T); + + /* A_limbs > N_limbs (but A <= N) */ + one_limb[0] = 5; // N = 5 + two_limbs[0] = 3; // A = 3 + two_limbs[1] = 0; + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, two_limbs, 2, one_limb, 1, T); + mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, one_limb, 1, T); + + /* A_limbs > N_limbs (and A > N) */ + one_limb[0] = 5; // N = 5 + two_limbs[0] = 7; // A = 7 + two_limbs[1] = 0; + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, two_limbs, 2, one_limb, 1, T); + mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, one_limb, 1, T); + + /* I != NULL but N is 1 */ + two_limbs[0] = 1; // N = 1 + two_limbs[1] = 0; + one_limb[0] = 1; // A = 1 + mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, one_limb, 1, T); + + /* + * Aliasing + */ + + /* Now use valid values for remaining tests */ + two_limbs[0] = 1; // N = 2^n + 1 + two_limbs[1] = 1; + one_limb[0] = 42; // A = 42 + + /* A aliased to N */ + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, two_limbs, 2, two_limbs, 2, T); + mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, two_limbs, 2, T); + + /* G aliased to A and N */ + memcpy(G, two_limbs, 2 * sizeof(mbedtls_mpi_uint)); + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, G, 2, G, 2, T); + mbedtls_mpi_core_gcd_modinv_odd(G, I, G, 2, G, 2, T); + + /* I != NULL, G aliased to N */ + memcpy(G, two_limbs, 2 * sizeof(mbedtls_mpi_uint)); + mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, G, 2, T); + + /* I != NULL, I aliased to N */ + memcpy(I, two_limbs, 2 * sizeof(mbedtls_mpi_uint)); + mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, I, 2, T); + + /* I aliased to A and N */ + memcpy(I, two_limbs, 2 * sizeof(mbedtls_mpi_uint)); + mbedtls_mpi_core_gcd_modinv_odd(G, I, I, 2, I, 2, T); + + /* + * Not specific to this function + */ + + /* A_limbs = 0 */ + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, one_limb, 0, two_limbs, 2, T); + mbedtls_mpi_core_gcd_modinv_odd(G, I, one_limb, 0, two_limbs, 2, T); + + /* A_limbs = N_limbs = 0 */ + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, one_limb, 0, two_limbs, 0, T); + mbedtls_mpi_core_gcd_modinv_odd(G, I, one_limb, 0, two_limbs, 0, T); + +exit: + mbedtls_free(G); + mbedtls_free(I); + mbedtls_free(T); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_TEST_HOOKS */ void mpi_core_div2_mod_odd(char *input_X, char *input_N, char *input_exp_X) { diff --git a/tests/suites/test_suite_bignum_core.misc.data b/tests/suites/test_suite_bignum_core.misc.data index 5d3c6753ce..e2ba5a61f7 100644 --- a/tests/suites/test_suite_bignum_core.misc.data +++ b/tests/suites/test_suite_bignum_core.misc.data @@ -535,6 +535,9 @@ mpi_core_gcd_modinv_odd:"7f2405d6de7db80a7bc":"1a84113636607520200d":"1":"15f158 GCD-modinv (almost) max iterations mpi_core_gcd_modinv_odd:"8000000000000000":"b26eb5721a2cb24c36acb4550b176671":"1":"77e1dd63583a6b3c8deefe7737862c89" +GCD-modinv preconditions not met +mpi_core_gcd_modinv_odd_preconditions: + Div2 mod odd: even value mpi_core_div2_mod_odd:"4":"7":"2" From ec35382a510bb9417407068f1b80a32d9ff25423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 24 Jul 2025 12:22:16 +0200 Subject: [PATCH 13/91] Try again to clarify connection with the paper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum_core.c | 47 ++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/library/bignum_core.c b/library/bignum_core.c index 8ac65e2202..8091694615 100644 --- a/library/bignum_core.c +++ b/library/bignum_core.c @@ -1056,39 +1056,36 @@ void mbedtls_mpi_core_div2_mod_odd(mbedtls_mpi_uint *X, * Pre-conditions: see public documentation. * * See https://www.jstage.jst.go.jp/article/transinf/E106.D/9/E106.D_2022ICP0009/_pdf - * This is a minor rewrite + adaptation from Algorithm 8: SICT-MI (p. 1403). + * + * The paper gives two computationally equivalent algorithms: Alg 7 (readable) + * and Alg 8 (constant-time). We use a third version that's hopefully both: * * u, v = A, N # N is called p in the paper but doesn't have to be prime * q, r = 0, 1 * repeat bits(A_limbs + N_limbs) times: - * d = v - u - * t1 = (u and v both odd) ? u : d - * t2 = (u and v both odd) ? d : (u odd) ? v : u + * d = v - u # t1 in Alg 7 + * t1 = (u and v both odd) ? u : d # t1 in Alg 8 + * t2 = (u and v both odd) ? d : (u odd) ? v : u # t2 in Alg 8 * t2 >>= 1 - * s = t1 <= t2 - * u, v = (s) ? t1, t2 : t2, t1 + * swap = t1 > t2 # similar to s, z in Alg 8 + * u, v = (swap) ? t2, t1 : t1, t2 * - * d = r - q mod N - * t1 = (u and v both odd) ? q : d # t3 in the paper - * t2 = (u and v both odd) ? d : (u odd) ? r : q # t4 in the paper - * t2 /= 2 mod N - * q, r = (s) ? t1, t2 : t2, t1 - * return v, q + * d = r - q mod N # t2 in Alg 7 + * t1 = (u and v both odd) ? q : d # t3 in Alg 8 + * t2 = (u and v both odd) ? d : (u odd) ? r : q # t4 Alg 8 + * t2 /= 2 mod N # see below (pre_com) + * q, r = (swap) ? t2, t1 : t1, t2 + * return v, q # v: GCD, see Alg 6; q: no mult by pre_com, see below * * The ternary operators in the above pseudo-code need to be realised in a - * constant-time fashion. For t1-t4, The paper does it by using a mixture of bit - * and (signed) arithmetic operations which is hardly readable; we'll use - * conditional assign instead. We'll use conditional swap for the final update. + * constant-time fashion. We use conditional assign for t1, t2 and conditional + * swap for the final update. (Note: the similarity between branches of Alg 7 + * are highlighted in tables 2 and 3 and the surrounding text.) * - * Note: for insight about the complicated expressions for t1-t4 in Alg 8, it is - * useful to look at Alg 7 (warning: different meanings for t1, t2, they're our - * d above), table 2 and the surrounding text. + * Also, we re-order operations, grouping things related to the inverse, which + * facilitates making its computation optional, and requires fewer temporaries. * - * The other minor rewrite compared to Alg 8 in the paper is we re-ordered - * operations, grouping things related to the inverse, which facilitates making - * its computation optional, and requires fewer temporaries. - * - * The only actual change from Alg 8 is dropping the trick with pre_comm, + * The only actual change from the paper is dropping the trick with pre_com, * which I think complicates things for no benefit. * See the comment on the big I != NULL block below for details. */ @@ -1207,8 +1204,8 @@ void mbedtls_mpi_core_gcd_modinv_odd(mbedtls_mpi_uint *G, * operations on u and v, ie also divide by 2 here (mod N). * * The paper uses a trick where it replaces division by 2 with - * multiplication by 2 here, and compensates in the end by doing a - * final multiplication, which is probably intended as an optimisation. + * multiplication by 2 here, and compensates in the end by multiplying + * by pre_com, which is probably intended as an optimisation. * * However I believe it's not actually an optimisation, since * constant-time modular multiplication by 2 (left-shift + conditional From 9646537e94fe16b0f113a1171c17e5457251ac7c Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Thu, 24 Jul 2025 15:25:00 +0100 Subject: [PATCH 14/91] Improve testing of mbedtls_mpi_gcd() and mbedtls_mpi_inv_mod() Signed-off-by: Felix Conway --- include/mbedtls/bignum.h | 7 +++- tests/suites/test_suite_bignum.function | 49 +++++++++++++++++++----- tests/suites/test_suite_bignum.misc.data | 3 ++ 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/include/mbedtls/bignum.h b/include/mbedtls/bignum.h index 1e1c06330f..ed0c4e798e 100644 --- a/include/mbedtls/bignum.h +++ b/include/mbedtls/bignum.h @@ -988,10 +988,13 @@ int mbedtls_mpi_gcd(mbedtls_mpi *G, const mbedtls_mpi *A, * \brief Compute the modular inverse: X = A^-1 mod N * * \param X The destination MPI. This must point to an initialized MPI. + * The value returned on success will be between [1, N-1]. * \param A The MPI to calculate the modular inverse of. This must point - * to an initialized MPI. + * to an initialized MPI. This value can be negative, in which + * case a positive answer will still be returned in \p X. * \param N The base of the modular inversion. This must point to an - * initialized MPI. + * initialized MPI. If this points to the same MPI as \p X, + * then the value returned in \p X will be incorrect. * * \return \c 0 if successful. * \return #MBEDTLS_ERR_MPI_ALLOC_FAILED if a memory allocation failed. diff --git a/tests/suites/test_suite_bignum.function b/tests/suites/test_suite_bignum.function index 36f1476d76..c94e7ccf85 100644 --- a/tests/suites/test_suite_bignum.function +++ b/tests/suites/test_suite_bignum.function @@ -390,12 +390,23 @@ void mpi_gcd(char *input_X, char *input_Y, mbedtls_mpi A, X, Y, Z; mbedtls_mpi_init(&A); mbedtls_mpi_init(&X); mbedtls_mpi_init(&Y); mbedtls_mpi_init(&Z); - TEST_ASSERT(mbedtls_test_read_mpi(&X, input_X) == 0); - TEST_ASSERT(mbedtls_test_read_mpi(&Y, input_Y) == 0); - TEST_ASSERT(mbedtls_test_read_mpi(&A, input_A) == 0); - TEST_ASSERT(mbedtls_mpi_gcd(&Z, &X, &Y) == 0); + TEST_EQUAL(mbedtls_test_read_mpi(&X, input_X), 0); + TEST_EQUAL(mbedtls_test_read_mpi(&Y, input_Y), 0); + TEST_EQUAL(mbedtls_test_read_mpi(&A, input_A), 0); + TEST_EQUAL(mbedtls_mpi_gcd(&Z, &X, &Y), 0); TEST_ASSERT(sign_is_valid(&Z)); - TEST_ASSERT(mbedtls_mpi_cmp_mpi(&Z, &A) == 0); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&Z, &A), 0); + + mbedtls_mpi *Z_alias_X = &X; + TEST_EQUAL(mbedtls_mpi_gcd(Z_alias_X, &X, &Y), 0); + TEST_ASSERT(sign_is_valid(Z_alias_X)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(Z_alias_X, &A), 0); + + mbedtls_mpi *Z_alias_Y = &Y; + TEST_EQUAL(mbedtls_mpi_gcd(Z_alias_Y, &X, &Y), 0); + TEST_ASSERT(sign_is_valid(Z_alias_Y)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(Z_alias_Y, &A), 0); + exit: mbedtls_mpi_free(&A); mbedtls_mpi_free(&X); mbedtls_mpi_free(&Y); mbedtls_mpi_free(&Z); @@ -1134,14 +1145,32 @@ void mpi_inv_mod(char *input_X, char *input_Y, int res; mbedtls_mpi_init(&X); mbedtls_mpi_init(&Y); mbedtls_mpi_init(&Z); mbedtls_mpi_init(&A); - TEST_ASSERT(mbedtls_test_read_mpi(&X, input_X) == 0); - TEST_ASSERT(mbedtls_test_read_mpi(&Y, input_Y) == 0); - TEST_ASSERT(mbedtls_test_read_mpi(&A, input_A) == 0); + TEST_EQUAL(mbedtls_test_read_mpi(&X, input_X), 0); + TEST_EQUAL(mbedtls_test_read_mpi(&Y, input_Y), 0); + TEST_EQUAL(mbedtls_test_read_mpi(&A, input_A), 0); res = mbedtls_mpi_inv_mod(&Z, &X, &Y); - TEST_ASSERT(res == div_result); + TEST_EQUAL(res, div_result); if (res == 0) { TEST_ASSERT(sign_is_valid(&Z)); - TEST_ASSERT(mbedtls_mpi_cmp_mpi(&Z, &A) == 0); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&Z, &A), 0); + } + + mbedtls_mpi *Z_alias_X = &X; + res = mbedtls_mpi_inv_mod(Z_alias_X, &X, &Y); + TEST_EQUAL(res, div_result); + if (res == 0) { + TEST_ASSERT(sign_is_valid(Z_alias_X)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(Z_alias_X, &A), 0); + } + + /* When Z is an alias of Y, the answer returned in Z is normally incorrect. */ + mbedtls_mpi *Z_alias_Y = &Y; + res = mbedtls_mpi_inv_mod(Z_alias_Y, &X, &Y); + TEST_EQUAL(res, div_result); + if (res == 0) { + TEST_ASSERT(sign_is_valid(Z_alias_Y)); + /* Testing if Z_alias_Y == &A is not useful as it is true sometimes, but is + often false. */ } exit: diff --git a/tests/suites/test_suite_bignum.misc.data b/tests/suites/test_suite_bignum.misc.data index 2e3ff1ecc0..d4c730059d 100644 --- a/tests/suites/test_suite_bignum.misc.data +++ b/tests/suites/test_suite_bignum.misc.data @@ -1504,6 +1504,9 @@ mpi_gcd:"136154c5dee27c04d296c5e29a32ad9fb923d66f5ce20ecab875aff2a8de964e668cc3e Test GCD: 0 < A = B mpi_gcd:"109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af" +Test GCD: A = B < 0 +mpi_gcd:"-9986dabb54d13cd9fe0d9da594a97e8372ab26ed98ff622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"-9986dabb54d13cd9fe0d9da594a97e8372ab26ed98ff622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"9986dabb54d13cd9fe0d9da594a97e8372ab26ed98ff622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af" + Base test mbedtls_mpi_inv_mod #1 mpi_inv_mod:"3":"b":"4":0 From 0904a742358d8aac71cec1add593305dd7e0e43d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 25 Jul 2025 09:33:20 +0200 Subject: [PATCH 15/91] Remove tests for 0 limbs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit That rule is common to the whole module and not a likely mistake to make. Also, the test was not really precise as G, I, T were oversized. Better remove it than give a false sense of security. Signed-off-by: Manuel Pégourié-Gonnard --- tests/suites/test_suite_bignum_core.function | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/suites/test_suite_bignum_core.function b/tests/suites/test_suite_bignum_core.function index 8ae1931cbc..55693dec27 100644 --- a/tests/suites/test_suite_bignum_core.function +++ b/tests/suites/test_suite_bignum_core.function @@ -1616,18 +1616,6 @@ void mpi_core_gcd_modinv_odd_preconditions() memcpy(I, two_limbs, 2 * sizeof(mbedtls_mpi_uint)); mbedtls_mpi_core_gcd_modinv_odd(G, I, I, 2, I, 2, T); - /* - * Not specific to this function - */ - - /* A_limbs = 0 */ - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, one_limb, 0, two_limbs, 2, T); - mbedtls_mpi_core_gcd_modinv_odd(G, I, one_limb, 0, two_limbs, 2, T); - - /* A_limbs = N_limbs = 0 */ - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, one_limb, 0, two_limbs, 0, T); - mbedtls_mpi_core_gcd_modinv_odd(G, I, one_limb, 0, two_limbs, 0, T); - exit: mbedtls_free(G); mbedtls_free(I); From be8983d394f687f073b830a04418a9b254bf1009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 25 Jul 2025 09:46:52 +0200 Subject: [PATCH 16/91] Use precise sizes for temporaries in test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- tests/suites/test_suite_bignum_core.function | 57 +++++++++++--------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/tests/suites/test_suite_bignum_core.function b/tests/suites/test_suite_bignum_core.function index 55693dec27..70f761d46f 100644 --- a/tests/suites/test_suite_bignum_core.function +++ b/tests/suites/test_suite_bignum_core.function @@ -1539,14 +1539,16 @@ void mpi_core_gcd_modinv_odd_preconditions() * crash (but just outputs garbage) when preconditions are not met. */ - mbedtls_mpi_uint two_limbs[2]; mbedtls_mpi_uint one_limb[1]; - mbedtls_mpi_uint *G = NULL, *I = NULL, *T = NULL; + mbedtls_mpi_uint two_limbs[2]; + mbedtls_mpi_uint three_limbs[3]; + mbedtls_mpi_uint *G = NULL, *I = NULL, *TG = NULL, *TI = NULL; - /* Large enough for all calls below */ + /* We'll always use a two-limbs N */ TEST_CALLOC(G, 2); TEST_CALLOC(I, 2); - TEST_CALLOC(T, 5 * 2); + TEST_CALLOC(TG, 4 * 2); // For I == NULL + TEST_CALLOC(TI, 5 * 2); // For I != NULL /* * Input values @@ -1556,35 +1558,39 @@ void mpi_core_gcd_modinv_odd_preconditions() two_limbs[0] = 2; // N = 2^n + 2 two_limbs[1] = 1; one_limb[0] = 42; // A = 42 - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, one_limb, 1, two_limbs, 2, T); - mbedtls_mpi_core_gcd_modinv_odd(G, I, one_limb, 1, two_limbs, 2, T); + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, one_limb, 1, two_limbs, 2, TG); + mbedtls_mpi_core_gcd_modinv_odd(G, I, one_limb, 1, two_limbs, 2, TI); /* A > N */ two_limbs[0] = 3; // N = 3 two_limbs[1] = 0; one_limb[0] = 42; // A = 42 - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, one_limb, 1, two_limbs, 2, T); - mbedtls_mpi_core_gcd_modinv_odd(G, I, one_limb, 1, two_limbs, 2, T); + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, one_limb, 1, two_limbs, 2, TG); + mbedtls_mpi_core_gcd_modinv_odd(G, I, one_limb, 1, two_limbs, 2, TI); /* A_limbs > N_limbs (but A <= N) */ - one_limb[0] = 5; // N = 5 - two_limbs[0] = 3; // A = 3 + two_limbs[0] = 3; // N = 3 two_limbs[1] = 0; - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, two_limbs, 2, one_limb, 1, T); - mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, one_limb, 1, T); + three_limbs[0] = 1; // A = 1 + three_limbs[1] = 0; + three_limbs[2] = 0; + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, three_limbs, 3, two_limbs, 2, TG); + mbedtls_mpi_core_gcd_modinv_odd(G, I, three_limbs, 3, two_limbs, 2, TI); /* A_limbs > N_limbs (and A > N) */ - one_limb[0] = 5; // N = 5 - two_limbs[0] = 7; // A = 7 + two_limbs[0] = 3; // N = 3 two_limbs[1] = 0; - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, two_limbs, 2, one_limb, 1, T); - mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, one_limb, 1, T); + three_limbs[0] = 0; // A = 2^2n + three_limbs[1] = 0; + three_limbs[2] = 1; + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, three_limbs, 3, two_limbs, 2, TG); + mbedtls_mpi_core_gcd_modinv_odd(G, I, three_limbs, 3, two_limbs, 2, TI); /* I != NULL but N is 1 */ two_limbs[0] = 1; // N = 1 two_limbs[1] = 0; one_limb[0] = 1; // A = 1 - mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, one_limb, 1, T); + mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, one_limb, 1, TI); /* * Aliasing @@ -1596,30 +1602,31 @@ void mpi_core_gcd_modinv_odd_preconditions() one_limb[0] = 42; // A = 42 /* A aliased to N */ - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, two_limbs, 2, two_limbs, 2, T); - mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, two_limbs, 2, T); + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, two_limbs, 2, two_limbs, 2, TG); + mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, two_limbs, 2, TI); /* G aliased to A and N */ memcpy(G, two_limbs, 2 * sizeof(mbedtls_mpi_uint)); - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, G, 2, G, 2, T); - mbedtls_mpi_core_gcd_modinv_odd(G, I, G, 2, G, 2, T); + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, G, 2, G, 2, TG); + mbedtls_mpi_core_gcd_modinv_odd(G, I, G, 2, G, 2, TI); /* I != NULL, G aliased to N */ memcpy(G, two_limbs, 2 * sizeof(mbedtls_mpi_uint)); - mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, G, 2, T); + mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, G, 2, TI); /* I != NULL, I aliased to N */ memcpy(I, two_limbs, 2 * sizeof(mbedtls_mpi_uint)); - mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, I, 2, T); + mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, I, 2, TI); /* I aliased to A and N */ memcpy(I, two_limbs, 2 * sizeof(mbedtls_mpi_uint)); - mbedtls_mpi_core_gcd_modinv_odd(G, I, I, 2, I, 2, T); + mbedtls_mpi_core_gcd_modinv_odd(G, I, I, 2, I, 2, TI); exit: mbedtls_free(G); mbedtls_free(I); - mbedtls_free(T); + mbedtls_free(TG); + mbedtls_free(TI); } /* END_CASE */ From eb346801263ba4612b5e29633bd55fe18943ca65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 25 Jul 2025 09:49:30 +0200 Subject: [PATCH 17/91] Use more meaningful names in test function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- tests/suites/test_suite_bignum_core.function | 84 ++++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/tests/suites/test_suite_bignum_core.function b/tests/suites/test_suite_bignum_core.function index 70f761d46f..759801906f 100644 --- a/tests/suites/test_suite_bignum_core.function +++ b/tests/suites/test_suite_bignum_core.function @@ -1539,9 +1539,9 @@ void mpi_core_gcd_modinv_odd_preconditions() * crash (but just outputs garbage) when preconditions are not met. */ - mbedtls_mpi_uint one_limb[1]; - mbedtls_mpi_uint two_limbs[2]; - mbedtls_mpi_uint three_limbs[3]; + mbedtls_mpi_uint A[1]; + mbedtls_mpi_uint N[2]; + mbedtls_mpi_uint AAA[3]; mbedtls_mpi_uint *G = NULL, *I = NULL, *TG = NULL, *TI = NULL; /* We'll always use a two-limbs N */ @@ -1555,71 +1555,71 @@ void mpi_core_gcd_modinv_odd_preconditions() */ /* N is not odd */ - two_limbs[0] = 2; // N = 2^n + 2 - two_limbs[1] = 1; - one_limb[0] = 42; // A = 42 - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, one_limb, 1, two_limbs, 2, TG); - mbedtls_mpi_core_gcd_modinv_odd(G, I, one_limb, 1, two_limbs, 2, TI); + N[0] = 2; // N = 2^n + 2 + N[1] = 1; + A[0] = 42; // A = 42 + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, A, 1, N, 2, TG); + mbedtls_mpi_core_gcd_modinv_odd(G, I, A, 1, N, 2, TI); /* A > N */ - two_limbs[0] = 3; // N = 3 - two_limbs[1] = 0; - one_limb[0] = 42; // A = 42 - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, one_limb, 1, two_limbs, 2, TG); - mbedtls_mpi_core_gcd_modinv_odd(G, I, one_limb, 1, two_limbs, 2, TI); + N[0] = 3; // N = 3 + N[1] = 0; + A[0] = 42; // A = 42 + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, A, 1, N, 2, TG); + mbedtls_mpi_core_gcd_modinv_odd(G, I, A, 1, N, 2, TI); /* A_limbs > N_limbs (but A <= N) */ - two_limbs[0] = 3; // N = 3 - two_limbs[1] = 0; - three_limbs[0] = 1; // A = 1 - three_limbs[1] = 0; - three_limbs[2] = 0; - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, three_limbs, 3, two_limbs, 2, TG); - mbedtls_mpi_core_gcd_modinv_odd(G, I, three_limbs, 3, two_limbs, 2, TI); + N[0] = 3; // N = 3 + N[1] = 0; + AAA[0] = 1; // A = 1 + AAA[1] = 0; + AAA[2] = 0; + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, AAA, 3, N, 2, TG); + mbedtls_mpi_core_gcd_modinv_odd(G, I, AAA, 3, N, 2, TI); /* A_limbs > N_limbs (and A > N) */ - two_limbs[0] = 3; // N = 3 - two_limbs[1] = 0; - three_limbs[0] = 0; // A = 2^2n - three_limbs[1] = 0; - three_limbs[2] = 1; - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, three_limbs, 3, two_limbs, 2, TG); - mbedtls_mpi_core_gcd_modinv_odd(G, I, three_limbs, 3, two_limbs, 2, TI); + N[0] = 3; // N = 3 + N[1] = 0; + AAA[0] = 0; // A = 2^2n + AAA[1] = 0; + AAA[2] = 1; + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, AAA, 3, N, 2, TG); + mbedtls_mpi_core_gcd_modinv_odd(G, I, AAA, 3, N, 2, TI); /* I != NULL but N is 1 */ - two_limbs[0] = 1; // N = 1 - two_limbs[1] = 0; - one_limb[0] = 1; // A = 1 - mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, one_limb, 1, TI); + N[0] = 1; // N = 1 + N[1] = 0; + A[0] = 1; // A = 1 + mbedtls_mpi_core_gcd_modinv_odd(G, I, N, 2, A, 1, TI); /* * Aliasing */ /* Now use valid values for remaining tests */ - two_limbs[0] = 1; // N = 2^n + 1 - two_limbs[1] = 1; - one_limb[0] = 42; // A = 42 + N[0] = 1; // N = 2^n + 1 + N[1] = 1; + A[0] = 42; // A = 42 /* A aliased to N */ - mbedtls_mpi_core_gcd_modinv_odd(G, NULL, two_limbs, 2, two_limbs, 2, TG); - mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, two_limbs, 2, TI); + mbedtls_mpi_core_gcd_modinv_odd(G, NULL, N, 2, N, 2, TG); + mbedtls_mpi_core_gcd_modinv_odd(G, I, N, 2, N, 2, TI); /* G aliased to A and N */ - memcpy(G, two_limbs, 2 * sizeof(mbedtls_mpi_uint)); + memcpy(G, N, 2 * sizeof(mbedtls_mpi_uint)); mbedtls_mpi_core_gcd_modinv_odd(G, NULL, G, 2, G, 2, TG); mbedtls_mpi_core_gcd_modinv_odd(G, I, G, 2, G, 2, TI); /* I != NULL, G aliased to N */ - memcpy(G, two_limbs, 2 * sizeof(mbedtls_mpi_uint)); - mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, G, 2, TI); + memcpy(G, N, 2 * sizeof(mbedtls_mpi_uint)); + mbedtls_mpi_core_gcd_modinv_odd(G, I, A, 1, G, 2, TI); /* I != NULL, I aliased to N */ - memcpy(I, two_limbs, 2 * sizeof(mbedtls_mpi_uint)); - mbedtls_mpi_core_gcd_modinv_odd(G, I, two_limbs, 2, I, 2, TI); + memcpy(I, N, 2 * sizeof(mbedtls_mpi_uint)); + mbedtls_mpi_core_gcd_modinv_odd(G, I, A, 1, I, 2, TI); /* I aliased to A and N */ - memcpy(I, two_limbs, 2 * sizeof(mbedtls_mpi_uint)); + memcpy(I, N, 2 * sizeof(mbedtls_mpi_uint)); mbedtls_mpi_core_gcd_modinv_odd(G, I, I, 2, I, 2, TI); exit: From 1527b69c7ff77c0610ccb659146218c18448d8a2 Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Mon, 28 Jul 2025 16:31:44 +0100 Subject: [PATCH 18/91] Clarify parameter documentation Signed-off-by: Felix Conway --- include/mbedtls/bignum.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/mbedtls/bignum.h b/include/mbedtls/bignum.h index ed0c4e798e..c67d653d7a 100644 --- a/include/mbedtls/bignum.h +++ b/include/mbedtls/bignum.h @@ -993,8 +993,9 @@ int mbedtls_mpi_gcd(mbedtls_mpi *G, const mbedtls_mpi *A, * to an initialized MPI. This value can be negative, in which * case a positive answer will still be returned in \p X. * \param N The base of the modular inversion. This must point to an - * initialized MPI. If this points to the same MPI as \p X, - * then the value returned in \p X will be incorrect. + * initialized MPI and be greater than one. If this points to + * the same MPI as \p X, then the value returned in \p X will + * be incorrect. * * \return \c 0 if successful. * \return #MBEDTLS_ERR_MPI_ALLOC_FAILED if a memory allocation failed. From f6d883c9280f32a3fa3ffcd6c7336f653bb3efcf Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Mon, 28 Jul 2025 16:32:14 +0100 Subject: [PATCH 19/91] Improve invmod and gcd handwritten tests Signed-off-by: Felix Conway --- tests/suites/test_suite_bignum.misc.data | 60 ++++++++++++++++++------ 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/tests/suites/test_suite_bignum.misc.data b/tests/suites/test_suite_bignum.misc.data index d4c730059d..74973ae562 100644 --- a/tests/suites/test_suite_bignum.misc.data +++ b/tests/suites/test_suite_bignum.misc.data @@ -1435,6 +1435,9 @@ mpi_gcd:"":"":"0" Test GCD: 0 (null), 0 (1 limb) mpi_gcd:"":"00":"0" +Test GCD: 0 (1 limb), 0 (1 limb) +mpi_gcd:"00":"00":"0" + Test GCD: 0 (null), 3 mpi_gcd:"":"03":"3" @@ -1462,49 +1465,49 @@ mpi_gcd:"06":"":"6" Test GCD: 6, 0 (1 limb) mpi_gcd:"06":"00":"6" -Test GCD: gcd=1, 0 < A < B +Test GCD: gcd=1, A is odd, B is odd, 0 < A < B mpi_gcd:"109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"33ae3764fd06a00cdc3cba5c45dc79a9edb4e67e4d057cc74139d531c25190d111775fc4a0f4439b8b1930bbd766e7b46f170601f316c8a18ff8d5cb5ca5581f168345d101edb462b7d93b7c520ccb8fb276b447a63d869203cc11f67a1122dc4da034218de85e39":"1" -Test GCD: gcd=1, 0 < B < A +Test GCD: gcd=1, A is odd, B is odd, 0 < B < A mpi_gcd:"33ae3764fd06a00cdc3cba5c45dc79a9edb4e67e4d057cc74139d531c25190d111775fc4a0f4439b8b1930bbd766e7b46f170601f316c8a18ff8d5cb5ca5581f168345d101edb462b7d93b7c520ccb8fb276b447a63d869203cc11f67a1122dc4da034218de85e39":"109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"1" -Test GCD: gcd=1, A > 0, B < 0 +Test GCD: gcd=1, A is odd, B is odd, A > 0, B < 0 mpi_gcd:"109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"-33ae3764fd06a00cdc3cba5c45dc79a9edb4e67e4d057cc74139d531c25190d111775fc4a0f4439b8b1930bbd766e7b46f170601f316c8a18ff8d5cb5ca5581f168345d101edb462b7d93b7c520ccb8fb276b447a63d869203cc11f67a1122dc4da034218de85e39":"1" -Test GCD: gcd=1, A < 0 < B, |A| < |B| +Test GCD: gcd=1, A is odd, B is odd, A < 0 < B, |A| < |B| mpi_gcd:"-109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"33ae3764fd06a00cdc3cba5c45dc79a9edb4e67e4d057cc74139d531c25190d111775fc4a0f4439b8b1930bbd766e7b46f170601f316c8a18ff8d5cb5ca5581f168345d101edb462b7d93b7c520ccb8fb276b447a63d869203cc11f67a1122dc4da034218de85e39":"1" -Test GCD: gcd=1, B < A < 0 +Test GCD: gcd=1, A is odd, B is odd, B < A < 0 mpi_gcd:"-109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"-33ae3764fd06a00cdc3cba5c45dc79a9edb4e67e4d057cc74139d531c25190d111775fc4a0f4439b8b1930bbd766e7b46f170601f316c8a18ff8d5cb5ca5581f168345d101edb462b7d93b7c520ccb8fb276b447a63d869203cc11f67a1122dc4da034218de85e39":"1" -Test GCD: gcd=2, 0 < A < B +Test GCD: gcd=2, A is even, B is even, 0 < A < B mpi_gcd:"213fc8ae290cdcadfba95b36d6d0dbe4e4495f6f0d19e9e1976f28a4d2650a797e17dd4c2b282ccca9a279b3fc1b3b4b2952fdc40461e25f6a869bce7f69f0204e4b402c4566363d485c744ca032073583be630d37b2f261af25f6e59b552e3b15002b5e":"675c6ec9fa0d4019b87974b88bb8f353db69ccfc9a0af98e8273aa6384a321a222eebf8941e8873716326177aecdcf68de2e0c03e62d91431ff1ab96b94ab03e2d068ba203db68c56fb276f8a419971f64ed688f4c7b0d24079823ecf42245b89b4068431bd0bc72":"2" -Test GCD: gcd=2, 0 < B < A +Test GCD: gcd=2, A is even, B is even, 0 < B < A mpi_gcd:"675c6ec9fa0d4019b87974b88bb8f353db69ccfc9a0af98e8273aa6384a321a222eebf8941e8873716326177aecdcf68de2e0c03e62d91431ff1ab96b94ab03e2d068ba203db68c56fb276f8a419971f64ed688f4c7b0d24079823ecf42245b89b4068431bd0bc72":"213fc8ae290cdcadfba95b36d6d0dbe4e4495f6f0d19e9e1976f28a4d2650a797e17dd4c2b282ccca9a279b3fc1b3b4b2952fdc40461e25f6a869bce7f69f0204e4b402c4566363d485c744ca032073583be630d37b2f261af25f6e59b552e3b15002b5e":"2" -Test GCD: gcd=3, 0 < A < B +Test GCD: gcd=3, A is odd, B is odd, 0 < A < B mpi_gcd:"31dfad053d934b04f97e08d2423949d7566e0f2693a6ded26326bcf73b978fb63d23cbf240bc4332fe73b68dfa28d8f0bdfc7ca60692d38f1fc9e9b5bf1ee8307570e0426819515bec8aae72f04b0ad0459d9493d38c6b9286b8f25868ffc5589f80410d":"9b0aa62ef713e02694b62f14d1956cfdc91eb37ae7107655c3ad7f9546f4b27334661f4de2dccad2a14b92338634b71d4d451205d94459e4afea816215f0085d4389d17305c91d28278bb274f62662af17641cd6f2b893b60b6435e36e336894e8e09c64a9b91aab":"3" -Test GCD: gcd=3, 0 < B < A +Test GCD: gcd=3, A is odd, B is odd, 0 < B < A mpi_gcd:"9b0aa62ef713e02694b62f14d1956cfdc91eb37ae7107655c3ad7f9546f4b27334661f4de2dccad2a14b92338634b71d4d451205d94459e4afea816215f0085d4389d17305c91d28278bb274f62662af17641cd6f2b893b60b6435e36e336894e8e09c64a9b91aab":"31dfad053d934b04f97e08d2423949d7566e0f2693a6ded26326bcf73b978fb63d23cbf240bc4332fe73b68dfa28d8f0bdfc7ca60692d38f1fc9e9b5bf1ee8307570e0426819515bec8aae72f04b0ad0459d9493d38c6b9286b8f25868ffc5589f80410d":"3" -Test GCD: gcd=4, 0 < A < B +Test GCD: gcd=4, A is even, B is even, 0 < A < B mpi_gcd:"427f915c5219b95bf752b66dada1b7c9c892bede1a33d3c32ede5149a4ca14f2fc2fba98565059995344f367f836769652a5fb8808c3c4bed50d379cfed3e0409c9680588acc6c7a90b8e89940640e6b077cc61a6f65e4c35e4bedcb36aa5c762a0056bc":"ceb8dd93f41a803370f2e9711771e6a7b6d399f93415f31d04e754c70946434445dd7f1283d10e6e2c64c2ef5d9b9ed1bc5c1807cc5b22863fe3572d7295607c5a0d174407b6d18adf64edf148332e3ec9dad11e98f61a480f3047d9e8448b713680d08637a178e4":"4" -Test GCD: gcd=4, 0 < B < A +Test GCD: gcd=4, A is even, B is even, 0 < B < A mpi_gcd:"ceb8dd93f41a803370f2e9711771e6a7b6d399f93415f31d04e754c70946434445dd7f1283d10e6e2c64c2ef5d9b9ed1bc5c1807cc5b22863fe3572d7295607c5a0d174407b6d18adf64edf148332e3ec9dad11e98f61a480f3047d9e8448b713680d08637a178e4":"427f915c5219b95bf752b66dada1b7c9c892bede1a33d3c32ede5149a4ca14f2fc2fba98565059995344f367f836769652a5fb8808c3c4bed50d379cfed3e0409c9680588acc6c7a90b8e89940640e6b077cc61a6f65e4c35e4bedcb36aa5c762a0056bc":"4" -Test GCD: gcd=6, 0 < A < B +Test GCD: gcd=6, A is even, B is even, 0 < A < B mpi_gcd:"63bf5a0a7b269609f2fc11a4847293aeacdc1e4d274dbda4c64d79ee772f1f6c7a4797e481788665fce76d1bf451b1e17bf8f94c0d25a71e3f93d36b7e3dd060eae1c084d032a2b7d9155ce5e09615a08b3b2927a718d7250d71e4b0d1ff8ab13f00821a":"136154c5dee27c04d296c5e29a32ad9fb923d66f5ce20ecab875aff2a8de964e668cc3e9bc5b995a5429724670c696e3a9a8a240bb288b3c95fd502c42be010ba8713a2e60b923a504f1764e9ec4cc55e2ec839ade571276c16c86bc6dc66d129d1c138c953723556":"6" -Test GCD: gcd=6, 0 < B < A +Test GCD: gcd=6, A is even, B is even, 0 < B < A mpi_gcd:"136154c5dee27c04d296c5e29a32ad9fb923d66f5ce20ecab875aff2a8de964e668cc3e9bc5b995a5429724670c696e3a9a8a240bb288b3c95fd502c42be010ba8713a2e60b923a504f1764e9ec4cc55e2ec839ade571276c16c86bc6dc66d129d1c138c953723556":"63bf5a0a7b269609f2fc11a4847293aeacdc1e4d274dbda4c64d79ee772f1f6c7a4797e481788665fce76d1bf451b1e17bf8f94c0d25a71e3f93d36b7e3dd060eae1c084d032a2b7d9155ce5e09615a08b3b2927a718d7250d71e4b0d1ff8ab13f00821a":"6" -Test GCD: 0 < A = B +Test GCD: A is odd, B is odd, 0 < A = B mpi_gcd:"109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af" -Test GCD: A = B < 0 +Test GCD: A is odd, B is odd, A = B < 0 mpi_gcd:"-9986dabb54d13cd9fe0d9da594a97e8372ab26ed98ff622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"-9986dabb54d13cd9fe0d9da594a97e8372ab26ed98ff622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"9986dabb54d13cd9fe0d9da594a97e8372ab26ed98ff622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af" Base test mbedtls_mpi_inv_mod #1 @@ -1516,6 +1519,18 @@ mpi_inv_mod:"3":"":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA Test mbedtls_mpi_inv_mod: mod 0 (1 limb) mpi_inv_mod:"3":"0":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA +Test mbedtls_mpi_inv_mod: 0 (null) mod positive +mpi_inv_mod:"":"25":"0":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE + +Test mbedtls_mpi_inv_mod: 0 (1 limb) mod positive +mpi_inv_mod:"00":"25":"0":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE + +Test mbedtls_mpi_inv_mod: 0 (null) mod 0 (null) +mpi_inv_mod:"":"":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +Test mbedtls_mpi_inv_mod: 0 (1 limb) mod 0 (1 limb) +mpi_inv_mod:"00":"00":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + Test mbedtls_mpi_inv_mod: mod negative mpi_inv_mod:"3":"-b":"4":MBEDTLS_ERR_MPI_BAD_INPUT_DATA @@ -1525,6 +1540,21 @@ mpi_inv_mod:"2":"4":"0":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE Test mbedtls_mpi_inv_mod: mod 1 mpi_inv_mod:"3":"1":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA +Test mbedtls_mpi_inv_mod: negative mod 1 +mpi_inv_mod:"-732487665ae082f75c44":"1":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +Test mbedtls_mpi_inv_mod: 1 mod 1 +mpi_inv_mod:"1":"1":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +Test mbedtls_mpi_inv_mod: larger positive mod 1 +mpi_inv_mod:"aaf97513ce987d99d9d934e":"1":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +Test mbedtls_mpi_inv_mod: 0 (null) mod 1 +mpi_inv_mod:"":"1":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +Test mbedtls_mpi_inv_mod: 0 (1 limb) mod 1 +mpi_inv_mod:"00":"1":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + Test mbedtls_mpi_inv_mod: 0 (null) ^-1 mpi_inv_mod:"":"11":"":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE From 8951916ac7108dea208888a4f75a82be3054e992 Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Tue, 29 Jul 2025 11:03:08 +0100 Subject: [PATCH 20/91] Fix pointer aliasing in bignum tests Signed-off-by: Felix Conway --- include/mbedtls/bignum.h | 4 +-- tests/suites/test_suite_bignum.function | 36 ++++++++++++------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/include/mbedtls/bignum.h b/include/mbedtls/bignum.h index c67d653d7a..b06aec9ce8 100644 --- a/include/mbedtls/bignum.h +++ b/include/mbedtls/bignum.h @@ -993,9 +993,7 @@ int mbedtls_mpi_gcd(mbedtls_mpi *G, const mbedtls_mpi *A, * to an initialized MPI. This value can be negative, in which * case a positive answer will still be returned in \p X. * \param N The base of the modular inversion. This must point to an - * initialized MPI and be greater than one. If this points to - * the same MPI as \p X, then the value returned in \p X will - * be incorrect. + * initialized MPI and be greater than one. * * \return \c 0 if successful. * \return #MBEDTLS_ERR_MPI_ALLOC_FAILED if a memory allocation failed. diff --git a/tests/suites/test_suite_bignum.function b/tests/suites/test_suite_bignum.function index c94e7ccf85..e2b9517683 100644 --- a/tests/suites/test_suite_bignum.function +++ b/tests/suites/test_suite_bignum.function @@ -397,15 +397,16 @@ void mpi_gcd(char *input_X, char *input_Y, TEST_ASSERT(sign_is_valid(&Z)); TEST_EQUAL(mbedtls_mpi_cmp_mpi(&Z, &A), 0); - mbedtls_mpi *Z_alias_X = &X; - TEST_EQUAL(mbedtls_mpi_gcd(Z_alias_X, &X, &Y), 0); - TEST_ASSERT(sign_is_valid(Z_alias_X)); - TEST_EQUAL(mbedtls_mpi_cmp_mpi(Z_alias_X, &A), 0); + /* Test pointer aliasing where &Z == &X and &Z == &Y. */ + TEST_EQUAL(mbedtls_test_read_mpi(&Z, input_X), 0); + TEST_EQUAL(mbedtls_mpi_gcd(&Z, /* X */ &Z, &Y), 0); + TEST_ASSERT(sign_is_valid(&Z)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&Z, &A), 0); - mbedtls_mpi *Z_alias_Y = &Y; - TEST_EQUAL(mbedtls_mpi_gcd(Z_alias_Y, &X, &Y), 0); - TEST_ASSERT(sign_is_valid(Z_alias_Y)); - TEST_EQUAL(mbedtls_mpi_cmp_mpi(Z_alias_Y, &A), 0); + TEST_EQUAL(mbedtls_test_read_mpi(&Z, input_Y), 0); + TEST_EQUAL(mbedtls_mpi_gcd(&Z, &X, /* Y */ &Z), 0); + TEST_ASSERT(sign_is_valid(&Z)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&Z, &A), 0); exit: @@ -1155,22 +1156,21 @@ void mpi_inv_mod(char *input_X, char *input_Y, TEST_EQUAL(mbedtls_mpi_cmp_mpi(&Z, &A), 0); } - mbedtls_mpi *Z_alias_X = &X; - res = mbedtls_mpi_inv_mod(Z_alias_X, &X, &Y); + /* Test pointer aliasing where &Z == &X and &Z == &Y. */ + TEST_EQUAL(mbedtls_test_read_mpi(&Z, input_X), 0); + res = mbedtls_mpi_inv_mod(&Z, /* X */ &Z, &Y); TEST_EQUAL(res, div_result); if (res == 0) { - TEST_ASSERT(sign_is_valid(Z_alias_X)); - TEST_EQUAL(mbedtls_mpi_cmp_mpi(Z_alias_X, &A), 0); + TEST_ASSERT(sign_is_valid(&Z)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&Z, &A), 0); } - /* When Z is an alias of Y, the answer returned in Z is normally incorrect. */ - mbedtls_mpi *Z_alias_Y = &Y; - res = mbedtls_mpi_inv_mod(Z_alias_Y, &X, &Y); + TEST_EQUAL(mbedtls_test_read_mpi(&Z, input_Y), 0); + res = mbedtls_mpi_inv_mod(&Z, &X, /* Y */ &Z); TEST_EQUAL(res, div_result); if (res == 0) { - TEST_ASSERT(sign_is_valid(Z_alias_Y)); - /* Testing if Z_alias_Y == &A is not useful as it is true sometimes, but is - often false. */ + TEST_ASSERT(sign_is_valid(&Z)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&Z, &A), 0); } exit: From 4c7c5c3f17ffde0bd6e77fe963af64db4803fcd5 Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Tue, 29 Jul 2025 12:10:03 +0100 Subject: [PATCH 21/91] Add more manual inv_mod tests Signed-off-by: Felix Conway --- tests/suites/test_suite_bignum.misc.data | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/suites/test_suite_bignum.misc.data b/tests/suites/test_suite_bignum.misc.data index 74973ae562..efecd43cf2 100644 --- a/tests/suites/test_suite_bignum.misc.data +++ b/tests/suites/test_suite_bignum.misc.data @@ -1531,9 +1531,27 @@ mpi_inv_mod:"":"":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA Test mbedtls_mpi_inv_mod: 0 (1 limb) mod 0 (1 limb) mpi_inv_mod:"00":"00":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA +Test mbedtls_mpi_inv_mod: 0 (null) mod 0 (1 limb) +mpi_inv_mod:"":"00":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +Test mbedtls_mpi_inv_mod: 0 (1 limb) mod 0 (null) +mpi_inv_mod:"00":"":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + Test mbedtls_mpi_inv_mod: mod negative mpi_inv_mod:"3":"-b":"4":MBEDTLS_ERR_MPI_BAD_INPUT_DATA +Test mbedtls_mpi_inv_mod: negative mod negative +mpi_inv_mod:"-3543a":"-f":"5":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +Test mbedtls_mpi_inv_mod: 1 mod negative +mpi_inv_mod:"1":"-f":"1":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +Test mbedtls_mpi_inv_mod: 0 (null) mod negative +mpi_inv_mod:"":"-f":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +Test mbedtls_mpi_inv_mod: 0 (1 limb) mod negative +mpi_inv_mod:"00":"-f":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + Test mbedtls_mpi_inv_mod: 2^-1 mod 4 mpi_inv_mod:"2":"4":"0":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE From fca43c79fbed637faa0f498b31a7a151d7463bfd Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Tue, 29 Jul 2025 15:34:28 +0100 Subject: [PATCH 22/91] Rework misleading comment Signed-off-by: Felix Conway --- tests/suites/test_suite_bignum.function | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/suites/test_suite_bignum.function b/tests/suites/test_suite_bignum.function index e2b9517683..f710bb5939 100644 --- a/tests/suites/test_suite_bignum.function +++ b/tests/suites/test_suite_bignum.function @@ -397,18 +397,18 @@ void mpi_gcd(char *input_X, char *input_Y, TEST_ASSERT(sign_is_valid(&Z)); TEST_EQUAL(mbedtls_mpi_cmp_mpi(&Z, &A), 0); - /* Test pointer aliasing where &Z == &X and &Z == &Y. */ + /* Test pointer aliasing where &Z == &X. */ TEST_EQUAL(mbedtls_test_read_mpi(&Z, input_X), 0); TEST_EQUAL(mbedtls_mpi_gcd(&Z, /* X */ &Z, &Y), 0); TEST_ASSERT(sign_is_valid(&Z)); TEST_EQUAL(mbedtls_mpi_cmp_mpi(&Z, &A), 0); + /* Test pointer aliasing where &Z == &Y. */ TEST_EQUAL(mbedtls_test_read_mpi(&Z, input_Y), 0); TEST_EQUAL(mbedtls_mpi_gcd(&Z, &X, /* Y */ &Z), 0); TEST_ASSERT(sign_is_valid(&Z)); TEST_EQUAL(mbedtls_mpi_cmp_mpi(&Z, &A), 0); - exit: mbedtls_mpi_free(&A); mbedtls_mpi_free(&X); mbedtls_mpi_free(&Y); mbedtls_mpi_free(&Z); } @@ -1156,7 +1156,7 @@ void mpi_inv_mod(char *input_X, char *input_Y, TEST_EQUAL(mbedtls_mpi_cmp_mpi(&Z, &A), 0); } - /* Test pointer aliasing where &Z == &X and &Z == &Y. */ + /* Test pointer aliasing where &Z == &X. */ TEST_EQUAL(mbedtls_test_read_mpi(&Z, input_X), 0); res = mbedtls_mpi_inv_mod(&Z, /* X */ &Z, &Y); TEST_EQUAL(res, div_result); @@ -1165,6 +1165,7 @@ void mpi_inv_mod(char *input_X, char *input_Y, TEST_EQUAL(mbedtls_mpi_cmp_mpi(&Z, &A), 0); } + /* Test pointer aliasing where &Z == &Y. */ TEST_EQUAL(mbedtls_test_read_mpi(&Z, input_Y), 0); res = mbedtls_mpi_inv_mod(&Z, &X, /* Y */ &Z); TEST_EQUAL(res, div_result); From bb50b5ab0e043462f087945cb9a6eba4cd6f4e25 Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Tue, 29 Jul 2025 15:36:19 +0100 Subject: [PATCH 23/91] Remove manual GCD tests that are now generated Signed-off-by: Felix Conway --- tests/suites/test_suite_bignum.misc.data | 45 ------------------------ 1 file changed, 45 deletions(-) diff --git a/tests/suites/test_suite_bignum.misc.data b/tests/suites/test_suite_bignum.misc.data index efecd43cf2..ad3129803d 100644 --- a/tests/suites/test_suite_bignum.misc.data +++ b/tests/suites/test_suite_bignum.misc.data @@ -1420,51 +1420,6 @@ Test mbedtls_mpi_exp_mod (N.n=3, RR.n=1 on 64 bit) depends_on:MBEDTLS_HAVE_INT64 mpi_exp_mod_min_RR:"10":"2":"100000000000000010000000000000001":"100":0 -Base test GCD #1 -mpi_gcd:"2b5":"261":"15" - -Base test GCD #2 -mpi_gcd:"6e4":"364":"1c" - -Base test GCD #3 -mpi_gcd:"2dcdb10b":"2050d306":"1" - -Test GCD: 0 (null), 0 (null) -mpi_gcd:"":"":"0" - -Test GCD: 0 (null), 0 (1 limb) -mpi_gcd:"":"00":"0" - -Test GCD: 0 (1 limb), 0 (1 limb) -mpi_gcd:"00":"00":"0" - -Test GCD: 0 (null), 3 -mpi_gcd:"":"03":"3" - -Test GCD: 0 (null), 6 -mpi_gcd:"":"06":"6" - -Test GCD: 0 (1 limb), 0 (null) -mpi_gcd:"00":"":"0" - -Test GCD: 0 (1 limb), 3 -mpi_gcd:"00":"03":"3" - -Test GCD: 0 (1 limb), 6 -mpi_gcd:"00":"06":"6" - -Test GCD: 3, 0 (null) -mpi_gcd:"03":"":"3" - -Test GCD: 3, 0 (1 limb) -mpi_gcd:"03":"00":"3" - -Test GCD: 6, 0 (null) -mpi_gcd:"06":"":"6" - -Test GCD: 6, 0 (1 limb) -mpi_gcd:"06":"00":"6" - Test GCD: gcd=1, A is odd, B is odd, 0 < A < B mpi_gcd:"109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"33ae3764fd06a00cdc3cba5c45dc79a9edb4e67e4d057cc74139d531c25190d111775fc4a0f4439b8b1930bbd766e7b46f170601f316c8a18ff8d5cb5ca5581f168345d101edb462b7d93b7c520ccb8fb276b447a63d869203cc11f67a1122dc4da034218de85e39":"1" From c51168039bd0e7c502666ad95bbc3001ef7b725c Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Tue, 29 Jul 2025 15:50:05 +0100 Subject: [PATCH 24/91] Clarify mpi_gdc() documentation when B is 0 Signed-off-by: Felix Conway --- include/mbedtls/bignum.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/mbedtls/bignum.h b/include/mbedtls/bignum.h index b06aec9ce8..297c0a6ba4 100644 --- a/include/mbedtls/bignum.h +++ b/include/mbedtls/bignum.h @@ -974,6 +974,8 @@ int mbedtls_mpi_random(mbedtls_mpi *X, * \brief Compute the greatest common divisor: G = gcd(A, B) * * \param G The destination MPI. This must point to an initialized MPI. + * This will be positive unless \p B is 0, in which case \p A + * will be returned, where \p A could be negative. * \param A The first operand. This must point to an initialized MPI. * \param B The second operand. This must point to an initialized MPI. * From e28bb8cbe6b52fd306bb26090d4f2f98c6fb80f3 Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Wed, 30 Jul 2025 09:15:24 +0100 Subject: [PATCH 25/91] Revert "Remove manual GCD tests that are now generated" This reverts commit bb50b5ab0e043462f087945cb9a6eba4cd6f4e25. Signed-off-by: Felix Conway --- tests/suites/test_suite_bignum.misc.data | 45 ++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/suites/test_suite_bignum.misc.data b/tests/suites/test_suite_bignum.misc.data index ad3129803d..efecd43cf2 100644 --- a/tests/suites/test_suite_bignum.misc.data +++ b/tests/suites/test_suite_bignum.misc.data @@ -1420,6 +1420,51 @@ Test mbedtls_mpi_exp_mod (N.n=3, RR.n=1 on 64 bit) depends_on:MBEDTLS_HAVE_INT64 mpi_exp_mod_min_RR:"10":"2":"100000000000000010000000000000001":"100":0 +Base test GCD #1 +mpi_gcd:"2b5":"261":"15" + +Base test GCD #2 +mpi_gcd:"6e4":"364":"1c" + +Base test GCD #3 +mpi_gcd:"2dcdb10b":"2050d306":"1" + +Test GCD: 0 (null), 0 (null) +mpi_gcd:"":"":"0" + +Test GCD: 0 (null), 0 (1 limb) +mpi_gcd:"":"00":"0" + +Test GCD: 0 (1 limb), 0 (1 limb) +mpi_gcd:"00":"00":"0" + +Test GCD: 0 (null), 3 +mpi_gcd:"":"03":"3" + +Test GCD: 0 (null), 6 +mpi_gcd:"":"06":"6" + +Test GCD: 0 (1 limb), 0 (null) +mpi_gcd:"00":"":"0" + +Test GCD: 0 (1 limb), 3 +mpi_gcd:"00":"03":"3" + +Test GCD: 0 (1 limb), 6 +mpi_gcd:"00":"06":"6" + +Test GCD: 3, 0 (null) +mpi_gcd:"03":"":"3" + +Test GCD: 3, 0 (1 limb) +mpi_gcd:"03":"00":"3" + +Test GCD: 6, 0 (null) +mpi_gcd:"06":"":"6" + +Test GCD: 6, 0 (1 limb) +mpi_gcd:"06":"00":"6" + Test GCD: gcd=1, A is odd, B is odd, 0 < A < B mpi_gcd:"109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"33ae3764fd06a00cdc3cba5c45dc79a9edb4e67e4d057cc74139d531c25190d111775fc4a0f4439b8b1930bbd766e7b46f170601f316c8a18ff8d5cb5ca5581f168345d101edb462b7d93b7c520ccb8fb276b447a63d869203cc11f67a1122dc4da034218de85e39":"1" From 7758aa340aca1909199ce545057c0b065e89b2cc Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Wed, 30 Jul 2025 09:20:16 +0100 Subject: [PATCH 26/91] Add GCD tests that return negative when b=0 Signed-off-by: Felix Conway --- tests/suites/test_suite_bignum.misc.data | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/suites/test_suite_bignum.misc.data b/tests/suites/test_suite_bignum.misc.data index efecd43cf2..c6701b2567 100644 --- a/tests/suites/test_suite_bignum.misc.data +++ b/tests/suites/test_suite_bignum.misc.data @@ -1465,6 +1465,12 @@ mpi_gcd:"06":"":"6" Test GCD: 6, 0 (1 limb) mpi_gcd:"06":"00":"6" +Test GCD: negative, 0 (null) +mpi_gcd:"-50000":"":"-50000" + +Test GCD: negative, 0 (1 limb) +mpi_gcd:"-a782374b2ee927df28802745833a":"00":"-a782374b2ee927df28802745833a" + Test GCD: gcd=1, A is odd, B is odd, 0 < A < B mpi_gcd:"109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"33ae3764fd06a00cdc3cba5c45dc79a9edb4e67e4d057cc74139d531c25190d111775fc4a0f4439b8b1930bbd766e7b46f170601f316c8a18ff8d5cb5ca5581f168345d101edb462b7d93b7c520ccb8fb276b447a63d869203cc11f67a1122dc4da034218de85e39":"1" From 1e89301a2d15c044d3a8dfb92b08953b52b794af Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Fri, 1 Aug 2025 13:57:26 +0100 Subject: [PATCH 27/91] Add GCD tests for (0, negative) inputs Signed-off-by: Felix Conway --- tests/suites/test_suite_bignum.misc.data | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/suites/test_suite_bignum.misc.data b/tests/suites/test_suite_bignum.misc.data index c6701b2567..5a4df9f2d8 100644 --- a/tests/suites/test_suite_bignum.misc.data +++ b/tests/suites/test_suite_bignum.misc.data @@ -1471,6 +1471,12 @@ mpi_gcd:"-50000":"":"-50000" Test GCD: negative, 0 (1 limb) mpi_gcd:"-a782374b2ee927df28802745833a":"00":"-a782374b2ee927df28802745833a" +Test GCD: 0 (null), negative +mpi_gcd:"":"-50000":"50000" + +Test GCD: 0 (1 limb), negative +mpi_gcd:"00":"-a782374b2ee927df28802745833a":"a782374b2ee927df28802745833a" + Test GCD: gcd=1, A is odd, B is odd, 0 < A < B mpi_gcd:"109fe45714866e56fdd4ad9b6b686df27224afb7868cf4f0cbb794526932853cbf0beea61594166654d13cd9fe0d9da594a97ee20230f12fb5434de73fb4f8102725a01622b31b1ea42e3a265019039ac1df31869bd97930d792fb72cdaa971d8a8015af":"33ae3764fd06a00cdc3cba5c45dc79a9edb4e67e4d057cc74139d531c25190d111775fc4a0f4439b8b1930bbd766e7b46f170601f316c8a18ff8d5cb5ca5581f168345d101edb462b7d93b7c520ccb8fb276b447a63d869203cc11f67a1122dc4da034218de85e39":"1" From 014d9d16fd58cb3c7192da932e6fa5e4b916c81c Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Fri, 1 Aug 2025 13:57:42 +0100 Subject: [PATCH 28/91] Update framework pointer Signed-off-by: Felix Conway --- framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework b/framework index 2a3e2c5ea0..c8dbffb028 160000 --- a/framework +++ b/framework @@ -1 +1 @@ -Subproject commit 2a3e2c5ea053c14b745dbdf41f609b1edc6a72fa +Subproject commit c8dbffb028675a8a80eead2f1c1c6d22b3447789 From bd7ede3f330d452288de2c170eeb2f60229a58cf Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Mon, 4 Aug 2025 11:33:48 +0100 Subject: [PATCH 29/91] bignum: add mpi wrapper for gcd_modinv Signed-off-by: Felix Conway --- library/bignum.c | 60 +++++++++++++++++++++++++++++++++++++++ library/bignum_internal.h | 26 +++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/library/bignum.c b/library/bignum.c index 424490951d..a9423598dc 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -1743,6 +1743,66 @@ int mbedtls_mpi_exp_mod_unsafe(mbedtls_mpi *X, const mbedtls_mpi *A, return mbedtls_mpi_exp_mod_optionally_safe(X, A, E, MBEDTLS_MPI_IS_PUBLIC, N, prec_RR); } +/* Constant-time GCD and/or modinv with odd modulus and A <= N */ +int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, + mbedtls_mpi *I, + const mbedtls_mpi *A, + const mbedtls_mpi *N) +{ + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + mbedtls_mpi local_g; + mbedtls_mpi_uint *T = NULL; + const size_t T_factor = I != NULL ? 5 : 4; + + /* Check requirements on A and N */ + if (mbedtls_mpi_cmp_int(A, 0) < 0 || + mbedtls_mpi_cmp_mpi(A, N) > 0 || + mbedtls_mpi_get_bit(N, 0) != 1 || + (I != NULL && mbedtls_mpi_cmp_int(N, 1) == 0)) { + return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; + } + + /* Check aliasing requirements */ + if (A == N || (I != NULL && (I == N || G == N))) { + return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; + } + + mbedtls_mpi_init(&local_g); + + if (G == NULL) { + G = &local_g; + } + + MBEDTLS_MPI_CHK(mbedtls_mpi_grow(G, N->n)); + G->s = 1; + if (I != NULL) { + MBEDTLS_MPI_CHK(mbedtls_mpi_grow(I, N->n)); + I->s = 1; + } + + T = mbedtls_calloc(sizeof(mbedtls_mpi_uint) * N->n, T_factor); + if (T == NULL) { + ret = MBEDTLS_ERR_MPI_ALLOC_FAILED; + goto cleanup; + } + + mbedtls_mpi_uint *Ip = I != NULL ? I->p : NULL; + size_t An = A->n <= N->n ? A->n : N->n; + mbedtls_mpi_core_gcd_modinv_odd(G->p, Ip, A->p, An, N->p, N->n, T); + + if (G->n > N->n) { + memset(G->p + N->n, 0, ciL * (G->n - N->n)); + } + if (I != NULL && I->n > N->n) { + memset(I->p + N->n, 0, ciL * (I->n - N->n)); + } + +cleanup: + mbedtls_mpi_free(&local_g); + mbedtls_free(T); + return ret; +} + /* * Greatest common divisor: G = gcd(A, B) (HAC 14.54) */ diff --git a/library/bignum_internal.h b/library/bignum_internal.h index aceaf55ea2..e5657f5b8f 100644 --- a/library/bignum_internal.h +++ b/library/bignum_internal.h @@ -47,4 +47,30 @@ int mbedtls_mpi_exp_mod_unsafe(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *E, const mbedtls_mpi *N, mbedtls_mpi *prec_RR); +/** + * \brief Compute GCD(A, N) and/or A^-1 mod N if it exists, + * in constant time. + * + * \warning Requires N to be odd, and 0 <= A <= N. + * + * \note G and I must not alias each other but may alias A or N. + * + * \param[out] G The GCD of \p A and \p N. + * This may be NULL, to only compute I. + * \param[out] I The inverse of \p A modulo \p N if it exists (that is, + * if \p G above is 1 on exit); indeterminate otherwise. + * This may be NULL, to only compute G. + * \param[in] A The 1st operand of GCD and number to invert. + * This value must be less than or equal to \p N. + * \param[in] N The 2nd operand of GCD and modulus for inversion. + * Must be odd or the results are indeterminate. + * + * \return \c 0 if successful. + * \return #MBEDTLS_ERR_MPI_ALLOC_FAILED if a memory allocation failed. + */ +int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, + mbedtls_mpi *I, + const mbedtls_mpi *A, + const mbedtls_mpi *N); + #endif /* bignum_internal.h */ From 54a94c15988aa0ef7bdf24dc6f86a35c9b39ee70 Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Mon, 4 Aug 2025 11:34:19 +0100 Subject: [PATCH 30/91] Adjust mpi_gcd_modinv_odd docs and precondition checking Signed-off-by: Felix Conway --- library/bignum.c | 2 +- library/bignum_internal.h | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/library/bignum.c b/library/bignum.c index a9423598dc..b09a851d8b 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -1763,7 +1763,7 @@ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, } /* Check aliasing requirements */ - if (A == N || (I != NULL && (I == N || G == N))) { + if (A == N || G == I || (I != NULL && (I == N || G == N))) { return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; } diff --git a/library/bignum_internal.h b/library/bignum_internal.h index e5657f5b8f..ee2220a25f 100644 --- a/library/bignum_internal.h +++ b/library/bignum_internal.h @@ -51,9 +51,14 @@ int mbedtls_mpi_exp_mod_unsafe(mbedtls_mpi *X, const mbedtls_mpi *A, * \brief Compute GCD(A, N) and/or A^-1 mod N if it exists, * in constant time. * - * \warning Requires N to be odd, and 0 <= A <= N. + * \warning Requires N to be odd, and 0 <= A <= N, and N > 1 if + * I != NULL. * - * \note G and I must not alias each other but may alias A or N. + * \note G and I must not alias each other. + * A and N must not alias each other. + * When I == NULL (computing only the GCD), G can alias A or N. + * When I != NULL (computing the modular inverse), G or I can + * alias A, but neither of them can alias N (the modulus). * * \param[out] G The GCD of \p A and \p N. * This may be NULL, to only compute I. @@ -67,6 +72,8 @@ int mbedtls_mpi_exp_mod_unsafe(mbedtls_mpi *X, const mbedtls_mpi *A, * * \return \c 0 if successful. * \return #MBEDTLS_ERR_MPI_ALLOC_FAILED if a memory allocation failed. + * \return #MBEDTLS_ERR_MPI_BAD_INPUT_DATA if preconditions were not + * met. */ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, mbedtls_mpi *I, From 38ec046c4bd035584be58bc1ca4644da4b413d29 Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Mon, 4 Aug 2025 11:34:45 +0100 Subject: [PATCH 31/91] Add mpi_gcd_modinv_odd test functions Signed-off-by: Felix Conway --- tests/suites/test_suite_bignum.function | 165 ++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/tests/suites/test_suite_bignum.function b/tests/suites/test_suite_bignum.function index 36f1476d76..b8af87fb34 100644 --- a/tests/suites/test_suite_bignum.function +++ b/tests/suites/test_suite_bignum.function @@ -1149,6 +1149,171 @@ exit: } /* END_CASE */ +/* BEGIN_CASE */ +void mpi_gcd_modinv_odd_both(char *input_A, char *input_N, + char *result_G, char *result_I, + int return_code) +{ + int has_inverse = strcmp(result_I, "no_inverse") ? 1 : 0; + mbedtls_mpi G, I, A, N, exp_G, exp_I; + int res; + mbedtls_mpi_init(&G); mbedtls_mpi_init(&I); mbedtls_mpi_init(&A); mbedtls_mpi_init(&N); + mbedtls_mpi_init(&exp_G); mbedtls_mpi_init(&exp_I); + TEST_EQUAL(mbedtls_test_read_mpi(&A, input_A), 0); + TEST_EQUAL(mbedtls_test_read_mpi(&N, input_N), 0); + TEST_EQUAL(mbedtls_test_read_mpi(&exp_G, result_G), 0); + if (has_inverse) { + TEST_EQUAL(mbedtls_test_read_mpi(&exp_I, result_I), 0); + } + + res = mbedtls_mpi_gcd_modinv_odd(&G, &I, &A, &N); + TEST_EQUAL(res, return_code); + if (res == 0) { + TEST_ASSERT(sign_is_valid(&G)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); + /* If there is no inverse then the value returned in I will be + * indeterminate, and so not useful or possible to test. */ + if (has_inverse) { + TEST_ASSERT(sign_is_valid(&I)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); + } + } + + /* Test pointer aliasing where &G == &A. */ + TEST_EQUAL(mbedtls_test_read_mpi(&G, input_A), 0); + res = mbedtls_mpi_gcd_modinv_odd(&G, &I, /* A */ &G, &N); + TEST_EQUAL(res, return_code); + if (res == 0) { + TEST_ASSERT(sign_is_valid(&G)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); + /* If there is no inverse then the value returned in I will be + * indeterminate, and so not useful or possible to test. */ + if (has_inverse) { + TEST_ASSERT(sign_is_valid(&I)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); + } + } + + /* Test pointer aliasing where &G == &N. This should fail. */ + TEST_EQUAL(mbedtls_test_read_mpi(&G, input_N), 0); + res = mbedtls_mpi_gcd_modinv_odd(&G, &I, &A, /* N */ &G); + TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); + + /* Test pointer aliasing where &I == &A. */ + TEST_EQUAL(mbedtls_test_read_mpi(&I, input_A), 0); + res = mbedtls_mpi_gcd_modinv_odd(&G, &I, /* A */ &I, &N); + TEST_EQUAL(res, return_code); + if (res == 0) { + TEST_ASSERT(sign_is_valid(&G)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); + /* If there is no inverse then the value returned in I will be + * indeterminate, and so not useful or possible to test. */ + if (has_inverse) { + TEST_ASSERT(sign_is_valid(&I)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); + } + } + + /* Test pointer aliasing where &I == &N. This should fail. */ + TEST_EQUAL(mbedtls_test_read_mpi(&I, input_N), 0); + res = mbedtls_mpi_gcd_modinv_odd(&G, &I, &A, /* N */ &I); + TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); + + /* Test pointer aliasing where &A == &N. This should fail. */ + res = mbedtls_mpi_gcd_modinv_odd(&G, &I, &A, /* N */ &A); + TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); +} +/* END_CASE */ + +/* BEGIN_CASE */ +void mpi_gcd_modinv_odd_only_gcd(char *input_A, char *input_N, + char *result_G, int return_code) +{ + mbedtls_mpi G, A, N, exp_G; + int res; + mbedtls_mpi_init(&G); mbedtls_mpi_init(&A); mbedtls_mpi_init(&N); + mbedtls_mpi_init(&exp_G); + TEST_EQUAL(mbedtls_test_read_mpi(&A, input_A), 0); + TEST_EQUAL(mbedtls_test_read_mpi(&N, input_N), 0); + TEST_EQUAL(mbedtls_test_read_mpi(&exp_G, result_G), 0); + + res = mbedtls_mpi_gcd_modinv_odd(&G, NULL, &A, &N); + TEST_EQUAL(res, return_code); + if (res == 0) { + TEST_ASSERT(sign_is_valid(&G)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); + } + + /* Test pointer aliasing where &G == &A. */ + TEST_EQUAL(mbedtls_test_read_mpi(&G, input_A), 0); + res = mbedtls_mpi_gcd_modinv_odd(&G, NULL, /* A */ &G, &N); + TEST_EQUAL(res, return_code); + if (res == 0) { + TEST_ASSERT(sign_is_valid(&G)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); + } + + /* Test pointer aliasing where &G == &N. */ + TEST_EQUAL(mbedtls_test_read_mpi(&G, input_N), 0); + res = mbedtls_mpi_gcd_modinv_odd(&G, NULL, &A, /* N */ &G); + TEST_EQUAL(res, return_code); + if (res == 0) { + TEST_ASSERT(sign_is_valid(&G)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); + } + + /* Test pointer aliasing where &A == &N. This should fail. */ + res = mbedtls_mpi_gcd_modinv_odd(&G, NULL, &A, /* N */ &A); + TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); +} +/* END_CASE */ + +/* BEGIN_CASE */ +void mpi_gcd_modinv_odd_only_modinv(char *input_A, char *input_N, + char *result_I, int return_code) +{ + int has_inverse = strcmp(result_I, "no_inverse") ? 1 : 0; + mbedtls_mpi I, A, N, exp_I; + int res; + mbedtls_mpi_init(&I); mbedtls_mpi_init(&A); mbedtls_mpi_init(&N); + mbedtls_mpi_init(&exp_I); + TEST_EQUAL(mbedtls_test_read_mpi(&A, input_A), 0); + TEST_EQUAL(mbedtls_test_read_mpi(&N, input_N), 0); + if (has_inverse) { + TEST_EQUAL(mbedtls_test_read_mpi(&exp_I, result_I), 0); + } + + res = mbedtls_mpi_gcd_modinv_odd(NULL, &I, &A, &N); + TEST_EQUAL(res, return_code); + /* If there is no inverse then the value returned in I will be + * indeterminate, and so not useful or possible to test. */ + if (res == 0 && has_inverse) { + TEST_ASSERT(sign_is_valid(&I)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); + } + + /* Test pointer aliasing where &I == &A. */ + TEST_EQUAL(mbedtls_test_read_mpi(&I, input_A), 0); + res = mbedtls_mpi_gcd_modinv_odd(NULL, &I, /* A */ &I, &N); + TEST_EQUAL(res, return_code); + /* If there is no inverse then the value returned in I will be + * indeterminate, and so not useful or possible to test. */ + if (res == 0 && has_inverse) { + TEST_ASSERT(sign_is_valid(&I)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); + } + + /* Test pointer aliasing where &I == &N. This should fail. */ + TEST_EQUAL(mbedtls_test_read_mpi(&I, input_N), 0); + res = mbedtls_mpi_gcd_modinv_odd(NULL, &I, &A, /* N */ &I); + TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); + + /* Test pointer aliasing where &A == &N. This should fail. */ + res = mbedtls_mpi_gcd_modinv_odd(NULL, &I, &A, /* N */ &A); + TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_GENPRIME */ void mpi_is_prime(char *input_X, int div_result) { From 45835d1bf20e6fcbfbfd141031ce9875fe15e151 Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Mon, 4 Aug 2025 11:35:15 +0100 Subject: [PATCH 32/91] Add handful of manual gcd_modinv_odd test cases Signed-off-by: Felix Conway --- tests/suites/test_suite_bignum.misc.data | 54 ++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/suites/test_suite_bignum.misc.data b/tests/suites/test_suite_bignum.misc.data index 2e3ff1ecc0..6afd7f62b4 100644 --- a/tests/suites/test_suite_bignum.misc.data +++ b/tests/suites/test_suite_bignum.misc.data @@ -1531,6 +1531,60 @@ mpi_inv_mod:"00":"11":"":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE Test mbedtls_mpi_inv_mod #1 mpi_inv_mod:"aa4df5cb14b4c31237f98bd1faf527c283c2d0f3eec89718664ba33f9762907c":"fffbbd660b94412ae61ead9c2906a344116e316a256fd387874c6c675b1d587d":"8d6a5c1d7adeae3e94b9bcd2c47e0d46e778bc8804a2cc25c02d775dc3d05b0c":0 +GCD-modinv wrapper: working, A < N +mpi_gcd_modinv_odd_both:"54a":"3999":"1":"30b5":0 + +GCD-modinv wrapper: no mod inverse, A = N +mpi_gcd_modinv_odd_both:"365":"365":"365":"no_inverse":0 + +GCD-modinv wrapper: no mod inverse, A < N +mpi_gcd_modinv_odd_both:"5a":"b9":"5":"no_inverse":0 + +GCD-modinv wrapper: bad inputs, A > N +mpi_gcd_modinv_odd_both:"3999":"54a":"":"":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +GCD-modinv wrapper: bad inputs, A < 0 +mpi_gcd_modinv_odd_both:"-5":"54a":"":"":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +GCD-modinv wrapper: bad inputs, N even +mpi_gcd_modinv_odd_both:"89":"540":"":"":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +GCD-modinv wrapper only gcd: working, A < N +mpi_gcd_modinv_odd_only_gcd:"1de3":"31d9":"7":0 + +GCD-modinv wrapper only gcd: working, A = N +mpi_gcd_modinv_odd_only_gcd:"365":"365":"365":0 + +GCD-modinv wrapper only gcd: working, no mod inverse, A < N +mpi_gcd_modinv_odd_only_gcd:"19e":"a47f":"9":0 + +GCD-modinv wrapper only gcd: bad inputs, A > N +mpi_gcd_modinv_odd_only_gcd:"319d":"1de3":"":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +GCD-modinv wrapper only gcd: bad inputs, A < 0 +mpi_gcd_modinv_odd_only_gcd:"-628ef":"991827f":"":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +GCD-modinv wrapper only gcd: bad inputs, N even +mpi_gcd_modinv_odd_only_gcd:"319d":"24":"":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +GCD-modinv wrapper only modinv: working, A < N +mpi_gcd_modinv_odd_only_modinv:"28c":"26f9":"84f":0 + +GCD-modinv wrapper only modinv: no mod inverse, A = N +mpi_gcd_modinv_odd_only_modinv:"365":"365":"no_inverse":0 + +GCD-modinv wrapper only modinv: no mod inverse, A < N +mpi_gcd_modinv_odd_only_modinv:"19e":"a47f":"no_inverse":0 + +GCD-modinv wrapper only modinv: bad inputs, A > N +mpi_gcd_modinv_odd_only_modinv:"26f9":"28c":"":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +GCD-modinv wrapper only modinv: bad inputs, A < 0 +mpi_gcd_modinv_odd_only_modinv:"-992f":"1000002":"":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + +GCD-modinv wrapper only modinv: bad inputs, N even +mpi_gcd_modinv_odd_only_modinv:"28c":"26f0":"":MBEDTLS_ERR_MPI_BAD_INPUT_DATA + Base test mbedtls_mpi_is_prime #1 depends_on:MBEDTLS_GENPRIME mpi_is_prime:"0":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE From fae58c4a0ce33935e9ecfb1bb74850921f9f46ef Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Mon, 4 Aug 2025 13:05:34 +0100 Subject: [PATCH 33/91] Fix memory leak Signed-off-by: Felix Conway --- tests/suites/test_suite_bignum.function | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/suites/test_suite_bignum.function b/tests/suites/test_suite_bignum.function index b8af87fb34..7454fb809d 100644 --- a/tests/suites/test_suite_bignum.function +++ b/tests/suites/test_suite_bignum.function @@ -1222,6 +1222,10 @@ void mpi_gcd_modinv_odd_both(char *input_A, char *input_N, /* Test pointer aliasing where &A == &N. This should fail. */ res = mbedtls_mpi_gcd_modinv_odd(&G, &I, &A, /* N */ &A); TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); + +exit: + mbedtls_mpi_free(&G); mbedtls_mpi_free(&I); mbedtls_mpi_free(&A); mbedtls_mpi_free(&N); + mbedtls_mpi_free(&exp_G); mbedtls_mpi_free(&exp_I); } /* END_CASE */ @@ -1265,6 +1269,10 @@ void mpi_gcd_modinv_odd_only_gcd(char *input_A, char *input_N, /* Test pointer aliasing where &A == &N. This should fail. */ res = mbedtls_mpi_gcd_modinv_odd(&G, NULL, &A, /* N */ &A); TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); + +exit: + mbedtls_mpi_free(&G); mbedtls_mpi_free(&A); mbedtls_mpi_free(&N); + mbedtls_mpi_free(&exp_G); } /* END_CASE */ @@ -1311,6 +1319,10 @@ void mpi_gcd_modinv_odd_only_modinv(char *input_A, char *input_N, /* Test pointer aliasing where &A == &N. This should fail. */ res = mbedtls_mpi_gcd_modinv_odd(NULL, &I, &A, /* N */ &A); TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); + +exit: + mbedtls_mpi_free(&I); mbedtls_mpi_free(&A); mbedtls_mpi_free(&N); + mbedtls_mpi_free(&exp_I); } /* END_CASE */ From f4df43b6c4c63a987431978e9d9eba4233a2bbf2 Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Mon, 4 Aug 2025 17:00:10 +0100 Subject: [PATCH 34/91] Fix gcd_invmod_odd wrapper when A is 0 (null) Signed-off-by: Felix Conway --- library/bignum.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/library/bignum.c b/library/bignum.c index b09a851d8b..2f2ce6a41e 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -1751,6 +1751,7 @@ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; mbedtls_mpi local_g; + mbedtls_mpi local_a; mbedtls_mpi_uint *T = NULL; const size_t T_factor = I != NULL ? 5 : 4; @@ -1767,6 +1768,16 @@ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; } + mbedtls_mpi_init(&local_a); + /* If A is 0 (null), then A->p will be null, which is an issue when A->p is + * passed to mbedtls_mpi_core_gcd_modinv_odd below, so set A to 0 (1 limb) + * in this case. */ + if (A->n == 0 && A->p == NULL) { + mbedtls_mpi_read_string(&local_a, 16, "00"); + } else { + mbedtls_mpi_copy(&local_a, A); + } + mbedtls_mpi_init(&local_g); if (G == NULL) { @@ -1787,8 +1798,8 @@ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, } mbedtls_mpi_uint *Ip = I != NULL ? I->p : NULL; - size_t An = A->n <= N->n ? A->n : N->n; - mbedtls_mpi_core_gcd_modinv_odd(G->p, Ip, A->p, An, N->p, N->n, T); + size_t An = local_a.n <= N->n ? local_a.n : N->n; + mbedtls_mpi_core_gcd_modinv_odd(G->p, Ip, local_a.p, An, N->p, N->n, T); if (G->n > N->n) { memset(G->p + N->n, 0, ciL * (G->n - N->n)); @@ -1799,6 +1810,7 @@ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, cleanup: mbedtls_mpi_free(&local_g); + mbedtls_mpi_free(&local_a); mbedtls_free(T); return ret; } From d9c4c9c44167a431bfe571ff531c64413eb20b28 Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Tue, 5 Aug 2025 14:33:32 +0100 Subject: [PATCH 35/91] Update mpi_gcd_invmod_odd() related comments/documentation Signed-off-by: Felix Conway --- library/bignum.c | 2 +- library/bignum_internal.h | 12 ++++++------ tests/suites/test_suite_bignum.function | 14 ++++---------- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/library/bignum.c b/library/bignum.c index 2f2ce6a41e..e141cda740 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -1764,7 +1764,7 @@ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, } /* Check aliasing requirements */ - if (A == N || G == I || (I != NULL && (I == N || G == N))) { + if (A == N || (I != NULL && (I == N || G == N))) { return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; } diff --git a/library/bignum_internal.h b/library/bignum_internal.h index ee2220a25f..f3f6fcbc8d 100644 --- a/library/bignum_internal.h +++ b/library/bignum_internal.h @@ -48,14 +48,14 @@ int mbedtls_mpi_exp_mod_unsafe(mbedtls_mpi *X, const mbedtls_mpi *A, mbedtls_mpi *prec_RR); /** - * \brief Compute GCD(A, N) and/or A^-1 mod N if it exists, - * in constant time. + * \brief A wrapper around a constant time function to compute + * GCD(A, N) and/or A^-1 mod N if it exists. * - * \warning Requires N to be odd, and 0 <= A <= N, and N > 1 if - * I != NULL. + * \warning Requires N to be odd, and 0 <= A <= N. Additionally, if + * I != NULL, requires N > 1. + * The wrapper part of this function is not constant time. * - * \note G and I must not alias each other. - * A and N must not alias each other. + * \note A and N must not alias each other. * When I == NULL (computing only the GCD), G can alias A or N. * When I != NULL (computing the modular inverse), G or I can * alias A, but neither of them can alias N (the modulus). diff --git a/tests/suites/test_suite_bignum.function b/tests/suites/test_suite_bignum.function index 7454fb809d..2a9d878fd0 100644 --- a/tests/suites/test_suite_bignum.function +++ b/tests/suites/test_suite_bignum.function @@ -1162,6 +1162,8 @@ void mpi_gcd_modinv_odd_both(char *input_A, char *input_N, TEST_EQUAL(mbedtls_test_read_mpi(&A, input_A), 0); TEST_EQUAL(mbedtls_test_read_mpi(&N, input_N), 0); TEST_EQUAL(mbedtls_test_read_mpi(&exp_G, result_G), 0); + /* If there is no inverse then the value returned in I will be + * indeterminate, and so not useful or possible to test. */ if (has_inverse) { TEST_EQUAL(mbedtls_test_read_mpi(&exp_I, result_I), 0); } @@ -1171,8 +1173,6 @@ void mpi_gcd_modinv_odd_both(char *input_A, char *input_N, if (res == 0) { TEST_ASSERT(sign_is_valid(&G)); TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); - /* If there is no inverse then the value returned in I will be - * indeterminate, and so not useful or possible to test. */ if (has_inverse) { TEST_ASSERT(sign_is_valid(&I)); TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); @@ -1186,8 +1186,6 @@ void mpi_gcd_modinv_odd_both(char *input_A, char *input_N, if (res == 0) { TEST_ASSERT(sign_is_valid(&G)); TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); - /* If there is no inverse then the value returned in I will be - * indeterminate, and so not useful or possible to test. */ if (has_inverse) { TEST_ASSERT(sign_is_valid(&I)); TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); @@ -1206,8 +1204,6 @@ void mpi_gcd_modinv_odd_both(char *input_A, char *input_N, if (res == 0) { TEST_ASSERT(sign_is_valid(&G)); TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); - /* If there is no inverse then the value returned in I will be - * indeterminate, and so not useful or possible to test. */ if (has_inverse) { TEST_ASSERT(sign_is_valid(&I)); TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); @@ -1287,14 +1283,14 @@ void mpi_gcd_modinv_odd_only_modinv(char *input_A, char *input_N, mbedtls_mpi_init(&exp_I); TEST_EQUAL(mbedtls_test_read_mpi(&A, input_A), 0); TEST_EQUAL(mbedtls_test_read_mpi(&N, input_N), 0); + /* If there is no inverse then the value returned in I will be + * indeterminate, and so not useful or possible to test. */ if (has_inverse) { TEST_EQUAL(mbedtls_test_read_mpi(&exp_I, result_I), 0); } res = mbedtls_mpi_gcd_modinv_odd(NULL, &I, &A, &N); TEST_EQUAL(res, return_code); - /* If there is no inverse then the value returned in I will be - * indeterminate, and so not useful or possible to test. */ if (res == 0 && has_inverse) { TEST_ASSERT(sign_is_valid(&I)); TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); @@ -1304,8 +1300,6 @@ void mpi_gcd_modinv_odd_only_modinv(char *input_A, char *input_N, TEST_EQUAL(mbedtls_test_read_mpi(&I, input_A), 0); res = mbedtls_mpi_gcd_modinv_odd(NULL, &I, /* A */ &I, &N); TEST_EQUAL(res, return_code); - /* If there is no inverse then the value returned in I will be - * indeterminate, and so not useful or possible to test. */ if (res == 0 && has_inverse) { TEST_ASSERT(sign_is_valid(&I)); TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); From eefdfe99a47d8d7b8a8c67f8f1e621d06e6042b2 Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Tue, 5 Aug 2025 14:35:53 +0100 Subject: [PATCH 36/91] Change A=0 (null) handling in mpi_gcd_invmod_odd() Signed-off-by: Felix Conway --- library/bignum.c | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/library/bignum.c b/library/bignum.c index e141cda740..53ff95eadd 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -1751,9 +1751,9 @@ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; mbedtls_mpi local_g; - mbedtls_mpi local_a; mbedtls_mpi_uint *T = NULL; const size_t T_factor = I != NULL ? 5 : 4; + const mbedtls_mpi_uint zero = 0; /* Check requirements on A and N */ if (mbedtls_mpi_cmp_int(A, 0) < 0 || @@ -1768,16 +1768,6 @@ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; } - mbedtls_mpi_init(&local_a); - /* If A is 0 (null), then A->p will be null, which is an issue when A->p is - * passed to mbedtls_mpi_core_gcd_modinv_odd below, so set A to 0 (1 limb) - * in this case. */ - if (A->n == 0 && A->p == NULL) { - mbedtls_mpi_read_string(&local_a, 16, "00"); - } else { - mbedtls_mpi_copy(&local_a, A); - } - mbedtls_mpi_init(&local_g); if (G == NULL) { @@ -1797,9 +1787,15 @@ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, goto cleanup; } + /* We have to handle G and I carefully as they could be aliased + * to A or N. */ mbedtls_mpi_uint *Ip = I != NULL ? I->p : NULL; - size_t An = local_a.n <= N->n ? local_a.n : N->n; - mbedtls_mpi_core_gcd_modinv_odd(G->p, Ip, local_a.p, An, N->p, N->n, T); + /* If A is 0 (null), then A->p would be null, which would be an issue if + * A->p was passed to mbedtls_mpi_core_gcd_modinv_odd below. */ + const mbedtls_mpi_uint *Ap = A->p != NULL ? A->p : &zero; + size_t An = A->p == NULL ? 0 : A->n; + An = A->n <= N->n ? A->n : N->n; + mbedtls_mpi_core_gcd_modinv_odd(G->p, Ip, Ap, An, N->p, N->n, T); if (G->n > N->n) { memset(G->p + N->n, 0, ciL * (G->n - N->n)); @@ -1810,7 +1806,6 @@ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, cleanup: mbedtls_mpi_free(&local_g); - mbedtls_mpi_free(&local_a); mbedtls_free(T); return ret; } From 49a2bc475036cc697aea8d00aa23a81231eef4c9 Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Tue, 5 Aug 2025 14:38:20 +0100 Subject: [PATCH 37/91] Add gcd_invmod_odd() tests where G/I are initialized to large numbers Signed-off-by: Felix Conway --- tests/suites/test_suite_bignum.function | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/suites/test_suite_bignum.function b/tests/suites/test_suite_bignum.function index 2a9d878fd0..9019053258 100644 --- a/tests/suites/test_suite_bignum.function +++ b/tests/suites/test_suite_bignum.function @@ -1219,6 +1219,20 @@ void mpi_gcd_modinv_odd_both(char *input_A, char *input_N, res = mbedtls_mpi_gcd_modinv_odd(&G, &I, &A, /* N */ &A); TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); + /* Test G & I initialized to large number with non-zero limbs. */ + TEST_EQUAL(mbedtls_test_read_mpi(&G, "c7420eb50e52ce18795a1020896787ce18dcd6b0"), 0); + TEST_EQUAL(mbedtls_test_read_mpi(&I, "5702c227ee207cca33eb7a6151531b50541a47ef"), 0); + res = mbedtls_mpi_gcd_modinv_odd(&G, &I, &A, &N); + TEST_EQUAL(res, return_code); + if (res == 0) { + TEST_ASSERT(sign_is_valid(&G)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); + if (has_inverse) { + TEST_ASSERT(sign_is_valid(&I)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); + } + } + exit: mbedtls_mpi_free(&G); mbedtls_mpi_free(&I); mbedtls_mpi_free(&A); mbedtls_mpi_free(&N); mbedtls_mpi_free(&exp_G); mbedtls_mpi_free(&exp_I); @@ -1266,6 +1280,15 @@ void mpi_gcd_modinv_odd_only_gcd(char *input_A, char *input_N, res = mbedtls_mpi_gcd_modinv_odd(&G, NULL, &A, /* N */ &A); TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); + /* Test G initialized to large number with non-zero limbs. */ + TEST_EQUAL(mbedtls_test_read_mpi(&G, "8d81032aaa52fb4d29831c7ed183fffee9baf169"), 0); + res = mbedtls_mpi_gcd_modinv_odd(&G, NULL, &A, &N); + TEST_EQUAL(res, return_code); + if (res == 0) { + TEST_ASSERT(sign_is_valid(&G)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); + } + exit: mbedtls_mpi_free(&G); mbedtls_mpi_free(&A); mbedtls_mpi_free(&N); mbedtls_mpi_free(&exp_G); @@ -1314,6 +1337,15 @@ void mpi_gcd_modinv_odd_only_modinv(char *input_A, char *input_N, res = mbedtls_mpi_gcd_modinv_odd(NULL, &I, &A, /* N */ &A); TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); + /* Test I initialized to large number with non-zero limbs. */ + TEST_EQUAL(mbedtls_test_read_mpi(&I, "bc0ccc030cb2d8b31e40e08fac727d2f4a8c9c1d"), 0); + res = mbedtls_mpi_gcd_modinv_odd(NULL, &I, &A, &N); + TEST_EQUAL(res, return_code); + if (res == 0 && has_inverse) { + TEST_ASSERT(sign_is_valid(&I)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); + } + exit: mbedtls_mpi_free(&I); mbedtls_mpi_free(&A); mbedtls_mpi_free(&N); mbedtls_mpi_free(&exp_I); From a1c95e378a24a4253cbccb4b58d5e6564906a6f7 Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Wed, 6 Aug 2025 09:54:11 +0100 Subject: [PATCH 38/91] Adjust mpi_gcd_modinv_odd() internals Signed-off-by: Felix Conway --- library/bignum.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/library/bignum.c b/library/bignum.c index 53ff95eadd..4f7ca16cda 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -1774,11 +1774,11 @@ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, G = &local_g; } + /* We can't modify the values of G or I before use in the main function, + * as they could be aliased to A or N. */ MBEDTLS_MPI_CHK(mbedtls_mpi_grow(G, N->n)); - G->s = 1; if (I != NULL) { MBEDTLS_MPI_CHK(mbedtls_mpi_grow(I, N->n)); - I->s = 1; } T = mbedtls_calloc(sizeof(mbedtls_mpi_uint) * N->n, T_factor); @@ -1787,16 +1787,19 @@ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, goto cleanup; } - /* We have to handle G and I carefully as they could be aliased - * to A or N. */ mbedtls_mpi_uint *Ip = I != NULL ? I->p : NULL; - /* If A is 0 (null), then A->p would be null, which would be an issue if - * A->p was passed to mbedtls_mpi_core_gcd_modinv_odd below. */ + /* If A is 0 (null), then A->p would be null, and A->n would be 0, + * which would be an issue if A->p and A->n were passed to + * mbedtls_mpi_core_gcd_modinv_odd below. */ const mbedtls_mpi_uint *Ap = A->p != NULL ? A->p : &zero; - size_t An = A->p == NULL ? 0 : A->n; - An = A->n <= N->n ? A->n : N->n; + size_t An = A->n >= N->n ? N->n : A->p != NULL ? A->n : 1; mbedtls_mpi_core_gcd_modinv_odd(G->p, Ip, Ap, An, N->p, N->n, T); + G->s = 1; + if (I != NULL) { + I->s = 1; + } + if (G->n > N->n) { memset(G->p + N->n, 0, ciL * (G->n - N->n)); } From 99270322ffac3546f813b1069fd6efb667cdce3f Mon Sep 17 00:00:00 2001 From: Felix Conway Date: Wed, 6 Aug 2025 10:20:00 +0100 Subject: [PATCH 39/91] Improve mpi_gcd_invmod_odd() tests when I/G has more limbs than N Signed-off-by: Felix Conway --- tests/suites/test_suite_bignum.function | 60 ++++++++++++++----------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/tests/suites/test_suite_bignum.function b/tests/suites/test_suite_bignum.function index 9019053258..690d2bf509 100644 --- a/tests/suites/test_suite_bignum.function +++ b/tests/suites/test_suite_bignum.function @@ -1219,17 +1219,21 @@ void mpi_gcd_modinv_odd_both(char *input_A, char *input_N, res = mbedtls_mpi_gcd_modinv_odd(&G, &I, &A, /* N */ &A); TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); - /* Test G & I initialized to large number with non-zero limbs. */ - TEST_EQUAL(mbedtls_test_read_mpi(&G, "c7420eb50e52ce18795a1020896787ce18dcd6b0"), 0); - TEST_EQUAL(mbedtls_test_read_mpi(&I, "5702c227ee207cca33eb7a6151531b50541a47ef"), 0); - res = mbedtls_mpi_gcd_modinv_odd(&G, &I, &A, &N); - TEST_EQUAL(res, return_code); - if (res == 0) { - TEST_ASSERT(sign_is_valid(&G)); - TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); - if (has_inverse) { - TEST_ASSERT(sign_is_valid(&I)); - TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); + /* Test G & I initialized to a number with more limbs than N. */ + if (N.n > 0) { + TEST_EQUAL(mbedtls_mpi_grow(&G, N.n * 2), 0); + memset(G.p, 0x2d, G.n * sizeof(mbedtls_mpi_uint)); + TEST_EQUAL(mbedtls_mpi_grow(&I, N.n * 2), 0); + memset(I.p, 0x2f, I.n * sizeof(mbedtls_mpi_uint)); + res = mbedtls_mpi_gcd_modinv_odd(&G, &I, &A, &N); + TEST_EQUAL(res, return_code); + if (res == 0) { + TEST_ASSERT(sign_is_valid(&G)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); + if (has_inverse) { + TEST_ASSERT(sign_is_valid(&I)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); + } } } @@ -1280,13 +1284,16 @@ void mpi_gcd_modinv_odd_only_gcd(char *input_A, char *input_N, res = mbedtls_mpi_gcd_modinv_odd(&G, NULL, &A, /* N */ &A); TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); - /* Test G initialized to large number with non-zero limbs. */ - TEST_EQUAL(mbedtls_test_read_mpi(&G, "8d81032aaa52fb4d29831c7ed183fffee9baf169"), 0); - res = mbedtls_mpi_gcd_modinv_odd(&G, NULL, &A, &N); - TEST_EQUAL(res, return_code); - if (res == 0) { - TEST_ASSERT(sign_is_valid(&G)); - TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); + /* Test G initialized to a number with more limbs than N. */ + if (N.n > 0) { + TEST_EQUAL(mbedtls_mpi_grow(&G, N.n * 2), 0); + memset(G.p, 0x2b, G.n * sizeof(mbedtls_mpi_uint)); + res = mbedtls_mpi_gcd_modinv_odd(&G, NULL, &A, &N); + TEST_EQUAL(res, return_code); + if (res == 0) { + TEST_ASSERT(sign_is_valid(&G)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&G, &exp_G), 0); + } } exit: @@ -1337,13 +1344,16 @@ void mpi_gcd_modinv_odd_only_modinv(char *input_A, char *input_N, res = mbedtls_mpi_gcd_modinv_odd(NULL, &I, &A, /* N */ &A); TEST_EQUAL(res, MBEDTLS_ERR_MPI_BAD_INPUT_DATA); - /* Test I initialized to large number with non-zero limbs. */ - TEST_EQUAL(mbedtls_test_read_mpi(&I, "bc0ccc030cb2d8b31e40e08fac727d2f4a8c9c1d"), 0); - res = mbedtls_mpi_gcd_modinv_odd(NULL, &I, &A, &N); - TEST_EQUAL(res, return_code); - if (res == 0 && has_inverse) { - TEST_ASSERT(sign_is_valid(&I)); - TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); + /* Test I initialized to a number with more limbs than N. */ + if (N.n > 0) { + TEST_EQUAL(mbedtls_mpi_grow(&I, N.n * 2), 0); + memset(I.p, 0x29, I.n * sizeof(mbedtls_mpi_uint)); + res = mbedtls_mpi_gcd_modinv_odd(NULL, &I, &A, &N); + TEST_EQUAL(res, return_code); + if (res == 0 && has_inverse) { + TEST_ASSERT(sign_is_valid(&I)); + TEST_EQUAL(mbedtls_mpi_cmp_mpi(&I, &exp_I), 0); + } } exit: From 71ee919dbeb550fdeab71af51c514033b85d73ce Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 25 Jul 2025 20:00:39 +0200 Subject: [PATCH 40/91] More meaningful test case names Signed-off-by: Gilles Peskine --- tests/suites/test_suite_cipher.aes.data | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/suites/test_suite_cipher.aes.data b/tests/suites/test_suite_cipher.aes.data index 99a662b83f..6e9481b704 100644 --- a/tests/suites/test_suite_cipher.aes.data +++ b/tests/suites/test_suite_cipher.aes.data @@ -1482,43 +1482,43 @@ AES-256 CBC - Encrypt and decrypt 32 bytes in multiple parts with PKCS7 padding depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH enc_dec_buf_multipart:MBEDTLS_CIPHER_AES_256_CBC:256:16:16:MBEDTLS_PADDING_PKCS7:16:16:0:32 -AES Decrypt test vector #0 +AES-128-CBC Decrypt test vector, invalid PKCS7 padding depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_PADDING_PKCS7:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"23f710842b9bb9c32f26648c786807ca":"00000000000000000000000000000000":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 -AES Decrypt test vector #1 +AES-128-CBC Decrypt test vector, no padding depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_NONE:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"23f710842b9bb9c32f26648c786807ca":"00000000000000000000000000000000":"":"":0:0 -AES Decrypt test vector #2 +AES-192-CBC Decrypt test vector, no padding depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH decrypt_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_NONE:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"707b1dbb0ffa40ef7d95def421233fae":"fffffffff80000000000000000000000":"":"":0:0 -AES Decrypt test vector #3 +AES-256-CBC Decrypt test vector, no padding depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH decrypt_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_NONE:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"49af6b372135acef10132e548f217b17":"ff000000000000000000000000000000":"":"":0:0 -AES Decrypt test vector #4 +AES-128-CFB Decrypt test vector depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CFB decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CFB128:-1:"fffffffe000000000000000000000000":"00000000000000000000000000000000":"1114bc2028009b923f0b01915ce5e7c4":"00000000000000000000000000000000":"":"":0:0: -AES Decrypt test vector #5 +AES-192-CFB Decrypt test vector depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CFB:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH decrypt_test_vec:MBEDTLS_CIPHER_AES_192_CFB128:-1:"ffffffffffffffffffffffffffffffffffffffffffe00000":"00000000000000000000000000000000":"60136703374f64e860b48ce31f930716":"00000000000000000000000000000000":"":"":0:0 -AES Decrypt test vector #6 +AES-256-CFB Decrypt test vector depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CFB:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH decrypt_test_vec:MBEDTLS_CIPHER_AES_256_CFB128:-1:"ffffffffff800000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"be66cfea2fecd6bf0ec7b4352c99bcaa":"00000000000000000000000000000000":"":"":0:0 -AES Decrypt test vector #7 +AES-128-OFB Decrypt test vector depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_OFB decrypt_test_vec:MBEDTLS_CIPHER_AES_128_OFB:-1:"2B7E151628AED2A6ABF7158809CF4F3C":"000102030405060708090A0B0C0D0E0F":"3B3FD92EB72DAD20333449F8E83CFB4A7789508d16918f03f53c52dac54ed8259740051e9c5fecf64344f7a82260edcc304c6528f659c77866a510d9c1d6ae5e":"6BC1BEE22E409F96E93D7E117393172AAE2D8A571E03AC9C9EB76FAC45AF8E5130C81C46A35CE411E5FBC1191A0A52EFF69F2445DF4F9B17AD2B417BE66C3710":"":"":0:0: -AES Decrypt test vector #8 +AES-192-OFB Decrypt test vector depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_OFB:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH decrypt_test_vec:MBEDTLS_CIPHER_AES_192_OFB:-1:"8E73B0F7DA0E6452C810F32B809079E562F8EAD2522C6B7B":"000102030405060708090A0B0C0D0E0F":"CDC80D6FDDF18CAB34C25909C99A4174fcc28b8d4c63837c09e81700c11004018d9a9aeac0f6596f559c6d4daf59a5f26d9f200857ca6c3e9cac524bd9acc92a":"6BC1BEE22E409F96E93D7E117393172AAE2D8A571E03AC9C9EB76FAC45AF8E5130C81C46A35CE411E5FBC1191A0A52EFF69F2445DF4F9B17AD2B417BE66C3710":"":"":0:0: -AES Decrypt test vector #9 +AES-256-OFB Decrypt test vector depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_OFB:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH decrypt_test_vec:MBEDTLS_CIPHER_AES_256_OFB:-1:"603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4":"000102030405060708090A0B0C0D0E0F":"DC7E84BFDA79164B7ECD8486985D38604febdc6740d20b3ac88f6ad82a4fb08d71ab47a086e86eedf39d1c5bba97c4080126141d67f37be8538f5a8be740e484":"6BC1BEE22E409F96E93D7E117393172AAE2D8A571E03AC9C9EB76FAC45AF8E5130C81C46A35CE411E5FBC1191A0A52EFF69F2445DF4F9B17AD2B417BE66C3710":"":"":0:0: From 5ee94d52a6df43cc2ac5c647e87d079165419ac4 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 27 Jul 2025 18:02:21 +0200 Subject: [PATCH 41/91] More variety of CBC decrypt tests Have tests without padding, with valid PKCS7 padding and with several kinds of invalid PKCS7 padding. #!/usr/bin/env python3 from Crypto.Cipher import AES KEYS = { 128: bytes.fromhex("ffffffffe00000000000000000000000"), 192: bytes.fromhex("000000000000000000000000000000000000000000000000"), 256: bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000"), } IV = bytes.fromhex("00000000000000000000000000000000") def decrypt_test_vec(cf, bits, mode, padded_hex, padding_length, note=''): depends = ['MBEDTLS_AES_C', 'MBEDTLS_CIPHER_MODE_CBC'] plaintext = bytes.fromhex(padded_hex) plaintext_length = len(plaintext) if bits != 128: depends.append('!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH') key = KEYS[bits] iv = IV result = '0' if mode == 'NONE': padding_description = 'no padding' assert padding_length == 0 else: depends.append('MBEDTLS_CIPHER_PADDING_' + mode) padding_description = mode if padding_length is None: result = 'MBEDTLS_ERR_CIPHER_INVALID_PADDING' plaintext_length = 0 else: plaintext_length -= padding_length cipher = AES.new(key, AES.MODE_CBC, iv=iv) ciphertext = cipher.encrypt(plaintext) function = 'decrypt_test_vec' cf_maybe = '' if cf: function += '_cf' cf_maybe = 'CF ' depends.append('HAVE_CONSTANT_TIME_AES') if note: note = f' ({note})' print(f'''\ {cf_maybe}AES-{bits}-CBC Decrypt test vector, {padding_description}{note} depends_on:{':'.join(depends)} {function}:MBEDTLS_CIPHER_AES_{bits}_CBC:MBEDTLS_PADDING_{mode}:"{key.hex()}":"{iv.hex()}":"{ciphertext.hex()}":"{plaintext[:plaintext_length].hex()}":"":"":{result}:0 ''') def emit_tests(cf): # Already existing tests decrypt_test_vec(cf, 128, 'NONE', "00000000000000000000000000000000", 0) decrypt_test_vec(cf, 192, 'NONE', "fffffffff80000000000000000000000", 0) decrypt_test_vec(cf, 256, 'NONE', "ff000000000000000000000000000000", 0) # New tests decrypt_test_vec(cf, 128, 'PKCS7', "00000000000000000000000000000001", 1, 'good pad 1') decrypt_test_vec(cf, 192, 'PKCS7', "fffffffff80000000000000000000001", 1, 'good pad 1') decrypt_test_vec(cf, 256, 'PKCS7', "ff000000000000000000000000000001", 1, 'good pad 1') decrypt_test_vec(cf, 128, 'PKCS7', "00000000000000000000000000000202", 2, 'good pad 2') decrypt_test_vec(cf, 192, 'PKCS7', "fffffffff80000000000000000000202", 2, 'good pad 2') decrypt_test_vec(cf, 256, 'PKCS7', "ff000000000000000000000000000202", 2, 'good pad 2') decrypt_test_vec(cf, 128, 'PKCS7', "2a0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", 15, 'good pad 15') decrypt_test_vec(cf, 192, 'PKCS7', "2a0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", 15, 'good pad 15') decrypt_test_vec(cf, 256, 'PKCS7', "2a0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", 15, 'good pad 15') decrypt_test_vec(cf, 128, 'PKCS7', "10101010101010101010101010101010", 16, 'good pad 16') decrypt_test_vec(cf, 192, 'PKCS7', "10101010101010101010101010101010", 16, 'good pad 16') decrypt_test_vec(cf, 256, 'PKCS7', "10101010101010101010101010101010", 16, 'good pad 16') decrypt_test_vec(cf, 128, 'PKCS7', "00000000000000000000000000000000", None, 'bad pad 0') decrypt_test_vec(cf, 192, 'PKCS7', "fffffffff80000000000000000000000", None, 'bad pad 0') decrypt_test_vec(cf, 256, 'PKCS7', "ff000000000000000000000000000000", None, 'bad pad 0') decrypt_test_vec(cf, 128, 'PKCS7', "00000000000000000000000000000102", None, 'bad pad 0102') decrypt_test_vec(cf, 192, 'PKCS7', "fffffffff80000000000000000000102", None, 'bad pad 0102') decrypt_test_vec(cf, 256, 'PKCS7', "ff000000000000000000000000000102", None, 'bad pad 0102') decrypt_test_vec(cf, 128, 'PKCS7', "1111111111111111111111111111111111111111111111111111111111111111", None, 'long, bad pad 17') decrypt_test_vec(cf, 192, 'PKCS7', "1111111111111111111111111111111111111111111111111111111111111111", None, 'long, bad pad 17') decrypt_test_vec(cf, 256, 'PKCS7', "1111111111111111111111111111111111111111111111111111111111111111", None, 'long, bad pad 17') decrypt_test_vec(cf, 128, 'PKCS7', "11111111111111111111111111111111", None, 'short, bad pad 17') decrypt_test_vec(cf, 192, 'PKCS7', "11111111111111111111111111111111", None, 'short, bad pad 17') decrypt_test_vec(cf, 256, 'PKCS7', "11111111111111111111111111111111", None, 'short, bad pad 17') emit_tests(False) Signed-off-by: Gilles Peskine --- tests/suites/test_suite_cipher.aes.data | 106 ++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 7 deletions(-) diff --git a/tests/suites/test_suite_cipher.aes.data b/tests/suites/test_suite_cipher.aes.data index 6e9481b704..dd8bf719e0 100644 --- a/tests/suites/test_suite_cipher.aes.data +++ b/tests/suites/test_suite_cipher.aes.data @@ -1482,22 +1482,114 @@ AES-256 CBC - Encrypt and decrypt 32 bytes in multiple parts with PKCS7 padding depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH enc_dec_buf_multipart:MBEDTLS_CIPHER_AES_256_CBC:256:16:16:MBEDTLS_PADDING_PKCS7:16:16:0:32 -AES-128-CBC Decrypt test vector, invalid PKCS7 padding -depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_PADDING_PKCS7:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 -decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"23f710842b9bb9c32f26648c786807ca":"00000000000000000000000000000000":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 - AES-128-CBC Decrypt test vector, no padding -depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_NONE:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"23f710842b9bb9c32f26648c786807ca":"00000000000000000000000000000000":"":"":0:0 AES-192-CBC Decrypt test vector, no padding -depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH decrypt_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_NONE:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"707b1dbb0ffa40ef7d95def421233fae":"fffffffff80000000000000000000000":"":"":0:0 AES-256-CBC Decrypt test vector, no padding -depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH decrypt_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_NONE:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"49af6b372135acef10132e548f217b17":"ff000000000000000000000000000000":"":"":0:0 +AES-128-CBC Decrypt test vector, PKCS7 (good pad 1) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"6dbd04d1579f6a7bee0842b9ae491588":"000000000000000000000000000000":"":"":0:0 + +AES-192-CBC Decrypt test vector, PKCS7 (good pad 1) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"593ebdf9a785f414cbed5d8a9eee1e4d":"fffffffff800000000000000000000":"":"":0:0 + +AES-256-CBC Decrypt test vector, PKCS7 (good pad 1) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"f1b27ac78b93f6b0ab9787d8827176e6":"ff0000000000000000000000000000":"":"":0:0 + +AES-128-CBC Decrypt test vector, PKCS7 (good pad 2) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"175334ced3166a22437861f4bcced178":"0000000000000000000000000000":"":"":0:0 + +AES-192-CBC Decrypt test vector, PKCS7 (good pad 2) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"5d9ee7bc7066e438582c86c165604f2e":"fffffffff8000000000000000000":"":"":0:0 + +AES-256-CBC Decrypt test vector, PKCS7 (good pad 2) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"cc71abea78b8e82c3791b52d3dba55e2":"ff00000000000000000000000000":"":"":0:0 + +AES-128-CBC Decrypt test vector, PKCS7 (good pad 15) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"7d340c60b9067883962c69766cf9ec35":"2a":"":"":0:0 + +AES-192-CBC Decrypt test vector, PKCS7 (good pad 15) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"48fef8aaa78c4a148f241aaf14866772":"2a":"":"":0:0 + +AES-256-CBC Decrypt test vector, PKCS7 (good pad 15) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"6ad50ca266a3e32024818f11839afb2f":"2a":"":"":0:0 + +AES-128-CBC Decrypt test vector, PKCS7 (good pad 16) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"e9844992c4b55bcaf8199d5df842adad":"":"":"":0:0 + +AES-192-CBC Decrypt test vector, PKCS7 (good pad 16) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"02bb292527e726fd51eb29894d6f0aad":"":"":"":0:0 + +AES-256-CBC Decrypt test vector, PKCS7 (good pad 16) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"1f788fe6d86c317549697fbf0c07fa43":"":"":"":0:0 + +AES-128-CBC Decrypt test vector, PKCS7 (bad pad 0) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"23f710842b9bb9c32f26648c786807ca":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-192-CBC Decrypt test vector, PKCS7 (bad pad 0) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"707b1dbb0ffa40ef7d95def421233fae":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-256-CBC Decrypt test vector, PKCS7 (bad pad 0) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"49af6b372135acef10132e548f217b17":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-128-CBC Decrypt test vector, PKCS7 (bad pad 0102) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"e651289760d35177eade56eae724f8fd":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-192-CBC Decrypt test vector, PKCS7 (bad pad 0102) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"f5b599490354e71a3b3fb5f1419fb971":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-256-CBC Decrypt test vector, PKCS7 (bad pad 0102) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"6852d318a0884a289a725c558e761e25":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-128-CBC Decrypt test vector, PKCS7 (long, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"9c336551cc31074ffcefc161bac686b7afa572c3b53bd14a1b98d201229ddd03":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-192-CBC Decrypt test vector, PKCS7 (long, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"33a31ec7605c85893872a467777f3ddb5c4271870e51a0c618f20a0efccc2bfc":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-256-CBC Decrypt test vector, PKCS7 (long, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"3e536c7917a695485ef046bda7c6a3e64f439d87aa0deb338029253bea0ba54f":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-128-CBC Decrypt test vector, PKCS7 (short, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"9c336551cc31074ffcefc161bac686b7":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-192-CBC Decrypt test vector, PKCS7 (short, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"33a31ec7605c85893872a467777f3ddb":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-256-CBC Decrypt test vector, PKCS7 (short, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"3e536c7917a695485ef046bda7c6a3e6":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + AES-128-CFB Decrypt test vector depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CFB decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CFB128:-1:"fffffffe000000000000000000000000":"00000000000000000000000000000000":"1114bc2028009b923f0b01915ce5e7c4":"00000000000000000000000000000000":"":"":0:0: From 54131a3dc6c639abf775073cca6d114e2c46d00c Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 7 Aug 2025 22:55:56 +0200 Subject: [PATCH 42/91] Move constant-time padding tests to a separate suite Make it easier to run just the tests that matter under constant-flow testing instrumentation. Signed-off-by: Gilles Peskine --- tests/suites/test_suite_cipher.constant_time.data | 14 ++++++++++++++ tests/suites/test_suite_cipher.padding.data | 15 --------------- 2 files changed, 14 insertions(+), 15 deletions(-) create mode 100644 tests/suites/test_suite_cipher.constant_time.data diff --git a/tests/suites/test_suite_cipher.constant_time.data b/tests/suites/test_suite_cipher.constant_time.data new file mode 100644 index 0000000000..c2af62e006 --- /dev/null +++ b/tests/suites/test_suite_cipher.constant_time.data @@ -0,0 +1,14 @@ +Constant-time PKCS7 padding, valid #1 +get_pkcs_padding:"00112233445566778899AABBCCDDEE01":0:15 + +Constant-time PKCS7 padding, valid #2 +get_pkcs_padding:"00112233445566778899AA0505050505":0:11 + +Constant-time PKCS7 padding, valid #3 +get_pkcs_padding:"10101010101010101010101010101010":0:0 + +Constant-time PKCS7 padding, invalid zero +get_pkcs_padding:"00112233445566778899AABBCCDDEE00":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +Constant-time PKCS7 padding, invalid > 16 +get_pkcs_padding:"00112233445566778899AABBCCDDEE11":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 diff --git a/tests/suites/test_suite_cipher.padding.data b/tests/suites/test_suite_cipher.padding.data index 85b14c1f2f..0370fb3d28 100644 --- a/tests/suites/test_suite_cipher.padding.data +++ b/tests/suites/test_suite_cipher.padding.data @@ -217,18 +217,3 @@ check_padding:MBEDTLS_PADDING_NONE:"DABBAD0001":0:5 Check no padding #3 (correct by definition) check_padding:MBEDTLS_PADDING_NONE:"":0:0 - -Constant-time PKCS7 padding, valid #1 -get_pkcs_padding:"00112233445566778899AABBCCDDEE01":0:15 - -Constant-time PKCS7 padding, valid #2 -get_pkcs_padding:"00112233445566778899AA0505050505":0:11 - -Constant-time PKCS7 padding, valid #3 -get_pkcs_padding:"10101010101010101010101010101010":0:0 - -Constant-time PKCS7 padding, invalid zero -get_pkcs_padding:"00112233445566778899AABBCCDDEE00":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 - -Constant-time PKCS7 padding, invalid > 16 -get_pkcs_padding:"00112233445566778899AABBCCDDEE11":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 From 580d1f49544003972d2786c1ed1217bdf59baf42 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 8 Aug 2025 00:06:06 +0200 Subject: [PATCH 43/91] Do dedicated constant-time testing in a few more configurations Do constant-time testing in a couple of configurations that give some interesting coverage; * In a configuration that's close to the default: `test_aes_only_128_bit_keys`. Having only 128-bit AES keys doesn't reduce the interesting scope much (except that it doesn't test 192-bit and 256-bit AES, but since that configuration uses hardware AES, we don't care about that part). * when PSA buffer copying is not done, i.e. when `MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS` is enabled. This will be very relevant for the upcoming PSA constant-time tests. Use Valgrind, since some of the interesting tests require constant-time AES, which for us means AESNI or AESCE, which MSan doesn't support. Signed-off-by: Gilles Peskine --- .../components-configuration-crypto.sh | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/scripts/components-configuration-crypto.sh b/tests/scripts/components-configuration-crypto.sh index 04c38f6c36..6b12bb9af5 100644 --- a/tests/scripts/components-configuration-crypto.sh +++ b/tests/scripts/components-configuration-crypto.sh @@ -5,6 +5,28 @@ # This file contains test components that are executed by all.sh +## test_with_valgrind tests/suites/SUITE.data [...] +## Run the specified test suite(s) with Valgrind. +test_with_valgrind () { + for data_file in "$@"; do + suite="${data_file##*/}"; suite="${suite%.data}" + exe="tests/$suite" + log_file="tests/MemoryChecker.$suite.log" + make -C tests "$suite" + valgrind -q --tool=memcheck --track-origins=yes --log-file="$log_file" "$exe" + not grep . -- "$log_file" + done +} + +## Run a small set of dedicated constant-time tests with Valgrind. +## Exclude very slow suites. +## Exclude suites that contain some constant-time tests, but whose focus +## isn't on constant-time tests. +test_with_valgrind_constant_time () { + declare GLOBIGNORE="tests/suites/test_suite_constant_time_hmac.data" + test_with_valgrind tests/suites/*constant_time*.data +} + ################################################################ #### Configuration Testing - Crypto ################################################################ @@ -31,6 +53,17 @@ component_test_psa_assume_exclusive_buffers () { make test } +component_test_psa_assume_exclusive_buffers_valgrind_cf () { + msg "build: full config + MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS, constant flow with Valgrind" + scripts/config.py full + scripts/config.py set MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS + scripts/config.py set MBEDTLS_TEST_CONSTANT_FLOW_VALGRIND + make lib + + msg "test: full config + MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS, constant flow with Valgrind, selected suites" + test_with_valgrind_constant_time tests/suites/*constant_time*.data +} + component_test_crypto_with_static_key_slots() { msg "build: crypto full + MBEDTLS_PSA_STATIC_KEY_SLOTS" scripts/config.py crypto_full @@ -2969,11 +3002,15 @@ component_test_aes_only_128_bit_keys () { msg "build: default config + AES_ONLY_128_BIT_KEY_LENGTH" scripts/config.py set MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH scripts/config.py unset MBEDTLS_PADLOCK_C + scripts/config.py set MBEDTLS_TEST_CONSTANT_FLOW_VALGRIND make CFLAGS='-O2 -Werror -Wall -Wextra' msg "test: default config + AES_ONLY_128_BIT_KEY_LENGTH" make test + + msg "test: default config + AES_ONLY_128_BIT_KEY_LENGTH constant flow with Valgrind, selected suites" + test_with_valgrind_constant_time } component_test_no_ctr_drbg_aes_only_128_bit_keys () { From df00d458a2a2266b3ac9ab4ced85028cf59d68ae Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 27 Jul 2025 18:03:26 +0200 Subject: [PATCH 44/91] Constant-flow AES-CBC multipart decrypt tests The main goal is to validate that unpadding is constant-time, including error reporting. Use a separate test function, not annotations in the existing function, so that the functional tests can run on any platform, and we know from test outcomes where we have run the constant-time tests. The tests can only be actually constant-time if AES is constant time, since AES computations are part of what is checked. Thus this requires hardware-accelerated AES. We can't run our AESNI (or AESCE?) code under Msan (it doesn't detect when memory is written from assembly code), so these tests can only be run with Valgrind. Same test data as the newly introduced functional tests. #!/usr/bin/env python3 from Crypto.Cipher import AES KEYS = { 128: bytes.fromhex("ffffffffe00000000000000000000000"), 192: bytes.fromhex("000000000000000000000000000000000000000000000000"), 256: bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000"), } IV = bytes.fromhex("00000000000000000000000000000000") def decrypt_test_vec(cf, bits, mode, padded_hex, padding_length, note=''): depends = ['MBEDTLS_AES_C', 'MBEDTLS_CIPHER_MODE_CBC'] plaintext = bytes.fromhex(padded_hex) plaintext_length = len(plaintext) if bits != 128: depends.append('!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH') key = KEYS[bits] iv = IV result = '0' if mode == 'NONE': padding_description = 'no padding' assert padding_length == 0 else: depends.append('MBEDTLS_CIPHER_PADDING_' + mode) padding_description = mode if padding_length is None: result = 'MBEDTLS_ERR_CIPHER_INVALID_PADDING' plaintext_length = 0 else: plaintext_length -= padding_length cipher = AES.new(key, AES.MODE_CBC, iv=iv) ciphertext = cipher.encrypt(plaintext) function = 'decrypt_test_vec' cf_maybe = '' if cf: function += '_cf' cf_maybe = 'CF ' depends.append('HAVE_CONSTANT_TIME_AES') if note: note = f' ({note})' print(f'''\ {cf_maybe}AES-{bits}-CBC Decrypt test vector, {padding_description}{note} depends_on:{':'.join(depends)} {function}:MBEDTLS_CIPHER_AES_{bits}_CBC:MBEDTLS_PADDING_{mode}:"{key.hex()}":"{iv.hex()}":"{ciphertext.hex()}":"{plaintext[:plaintext_length].hex()}":"":"":{result}:0 ''') def emit_tests(cf): # Already existing tests decrypt_test_vec(cf, 128, 'NONE', "00000000000000000000000000000000", 0) decrypt_test_vec(cf, 192, 'NONE', "fffffffff80000000000000000000000", 0) decrypt_test_vec(cf, 256, 'NONE', "ff000000000000000000000000000000", 0) # New tests decrypt_test_vec(cf, 128, 'PKCS7', "00000000000000000000000000000001", 1, 'good pad 1') decrypt_test_vec(cf, 192, 'PKCS7', "fffffffff80000000000000000000001", 1, 'good pad 1') decrypt_test_vec(cf, 256, 'PKCS7', "ff000000000000000000000000000001", 1, 'good pad 1') decrypt_test_vec(cf, 128, 'PKCS7', "00000000000000000000000000000202", 2, 'good pad 2') decrypt_test_vec(cf, 192, 'PKCS7', "fffffffff80000000000000000000202", 2, 'good pad 2') decrypt_test_vec(cf, 256, 'PKCS7', "ff000000000000000000000000000202", 2, 'good pad 2') decrypt_test_vec(cf, 128, 'PKCS7', "2a0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", 15, 'good pad 15') decrypt_test_vec(cf, 192, 'PKCS7', "2a0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", 15, 'good pad 15') decrypt_test_vec(cf, 256, 'PKCS7', "2a0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", 15, 'good pad 15') decrypt_test_vec(cf, 128, 'PKCS7', "10101010101010101010101010101010", 16, 'good pad 16') decrypt_test_vec(cf, 192, 'PKCS7', "10101010101010101010101010101010", 16, 'good pad 16') decrypt_test_vec(cf, 256, 'PKCS7', "10101010101010101010101010101010", 16, 'good pad 16') decrypt_test_vec(cf, 128, 'PKCS7', "00000000000000000000000000000000", None, 'bad pad 0') decrypt_test_vec(cf, 192, 'PKCS7', "fffffffff80000000000000000000000", None, 'bad pad 0') decrypt_test_vec(cf, 256, 'PKCS7', "ff000000000000000000000000000000", None, 'bad pad 0') decrypt_test_vec(cf, 128, 'PKCS7', "00000000000000000000000000000102", None, 'bad pad 0102') decrypt_test_vec(cf, 192, 'PKCS7', "fffffffff80000000000000000000102", None, 'bad pad 0102') decrypt_test_vec(cf, 256, 'PKCS7', "ff000000000000000000000000000102", None, 'bad pad 0102') decrypt_test_vec(cf, 128, 'PKCS7', "1111111111111111111111111111111111111111111111111111111111111111", None, 'long, bad pad 17') decrypt_test_vec(cf, 192, 'PKCS7', "1111111111111111111111111111111111111111111111111111111111111111", None, 'long, bad pad 17') decrypt_test_vec(cf, 256, 'PKCS7', "1111111111111111111111111111111111111111111111111111111111111111", None, 'long, bad pad 17') decrypt_test_vec(cf, 128, 'PKCS7', "11111111111111111111111111111111", None, 'short, bad pad 17') decrypt_test_vec(cf, 192, 'PKCS7', "11111111111111111111111111111111", None, 'short, bad pad 17') decrypt_test_vec(cf, 256, 'PKCS7', "11111111111111111111111111111111", None, 'short, bad pad 17') emit_tests(True) Signed-off-by: Gilles Peskine --- .../test_suite_cipher.constant_time.data | 108 ++++++++++++++++++ tests/suites/test_suite_cipher.function | 88 ++++++++++++++ 2 files changed, 196 insertions(+) diff --git a/tests/suites/test_suite_cipher.constant_time.data b/tests/suites/test_suite_cipher.constant_time.data index c2af62e006..af608044bb 100644 --- a/tests/suites/test_suite_cipher.constant_time.data +++ b/tests/suites/test_suite_cipher.constant_time.data @@ -12,3 +12,111 @@ get_pkcs_padding:"00112233445566778899AABBCCDDEE00":MBEDTLS_ERR_CIPHER_INVALID_P Constant-time PKCS7 padding, invalid > 16 get_pkcs_padding:"00112233445566778899AABBCCDDEE11":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-128-CBC Decrypt test vector, no padding +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_NONE:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"23f710842b9bb9c32f26648c786807ca":"00000000000000000000000000000000":"":"":0:0 + +CF AES-192-CBC Decrypt test vector, no padding +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_NONE:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"707b1dbb0ffa40ef7d95def421233fae":"fffffffff80000000000000000000000":"":"":0:0 + +CF AES-256-CBC Decrypt test vector, no padding +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_NONE:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"49af6b372135acef10132e548f217b17":"ff000000000000000000000000000000":"":"":0:0 + +CF AES-128-CBC Decrypt test vector, PKCS7 (good pad 1) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"6dbd04d1579f6a7bee0842b9ae491588":"000000000000000000000000000000":"":"":0:0 + +CF AES-192-CBC Decrypt test vector, PKCS7 (good pad 1) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"593ebdf9a785f414cbed5d8a9eee1e4d":"fffffffff800000000000000000000":"":"":0:0 + +CF AES-256-CBC Decrypt test vector, PKCS7 (good pad 1) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"f1b27ac78b93f6b0ab9787d8827176e6":"ff0000000000000000000000000000":"":"":0:0 + +CF AES-128-CBC Decrypt test vector, PKCS7 (good pad 2) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"175334ced3166a22437861f4bcced178":"0000000000000000000000000000":"":"":0:0 + +CF AES-192-CBC Decrypt test vector, PKCS7 (good pad 2) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"5d9ee7bc7066e438582c86c165604f2e":"fffffffff8000000000000000000":"":"":0:0 + +CF AES-256-CBC Decrypt test vector, PKCS7 (good pad 2) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"cc71abea78b8e82c3791b52d3dba55e2":"ff00000000000000000000000000":"":"":0:0 + +CF AES-128-CBC Decrypt test vector, PKCS7 (good pad 15) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"7d340c60b9067883962c69766cf9ec35":"2a":"":"":0:0 + +CF AES-192-CBC Decrypt test vector, PKCS7 (good pad 15) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"48fef8aaa78c4a148f241aaf14866772":"2a":"":"":0:0 + +CF AES-256-CBC Decrypt test vector, PKCS7 (good pad 15) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"6ad50ca266a3e32024818f11839afb2f":"2a":"":"":0:0 + +CF AES-128-CBC Decrypt test vector, PKCS7 (good pad 16) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"e9844992c4b55bcaf8199d5df842adad":"":"":"":0:0 + +CF AES-192-CBC Decrypt test vector, PKCS7 (good pad 16) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"02bb292527e726fd51eb29894d6f0aad":"":"":"":0:0 + +CF AES-256-CBC Decrypt test vector, PKCS7 (good pad 16) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"1f788fe6d86c317549697fbf0c07fa43":"":"":"":0:0 + +CF AES-128-CBC Decrypt test vector, PKCS7 (bad pad 0) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"23f710842b9bb9c32f26648c786807ca":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-192-CBC Decrypt test vector, PKCS7 (bad pad 0) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"707b1dbb0ffa40ef7d95def421233fae":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-256-CBC Decrypt test vector, PKCS7 (bad pad 0) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"49af6b372135acef10132e548f217b17":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-128-CBC Decrypt test vector, PKCS7 (bad pad 0102) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"e651289760d35177eade56eae724f8fd":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-192-CBC Decrypt test vector, PKCS7 (bad pad 0102) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"f5b599490354e71a3b3fb5f1419fb971":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-256-CBC Decrypt test vector, PKCS7 (bad pad 0102) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"6852d318a0884a289a725c558e761e25":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-128-CBC Decrypt test vector, PKCS7 (long, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"9c336551cc31074ffcefc161bac686b7afa572c3b53bd14a1b98d201229ddd03":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-192-CBC Decrypt test vector, PKCS7 (long, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"33a31ec7605c85893872a467777f3ddb5c4271870e51a0c618f20a0efccc2bfc":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-256-CBC Decrypt test vector, PKCS7 (long, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"3e536c7917a695485ef046bda7c6a3e64f439d87aa0deb338029253bea0ba54f":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-128-CBC Decrypt test vector, PKCS7 (short, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"9c336551cc31074ffcefc161bac686b7":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-192-CBC Decrypt test vector, PKCS7 (short, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"33a31ec7605c85893872a467777f3ddb":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-256-CBC Decrypt test vector, PKCS7 (short, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"3e536c7917a695485ef046bda7c6a3e6":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 diff --git a/tests/suites/test_suite_cipher.function b/tests/suites/test_suite_cipher.function index 2856ae5699..37a77e5687 100644 --- a/tests/suites/test_suite_cipher.function +++ b/tests/suites/test_suite_cipher.function @@ -14,6 +14,20 @@ #define MBEDTLS_CIPHER_AUTH_CRYPT #endif +/* Our software AES implementation is not constant-time. For constant-time + * testing involving AES, require a hardware-assisted AES that is + * constant-time. + * + * We assume that if the hardware-assisted version is available in the build, + * it will be available at runtime. The AES tests will fail if run on a + * processor without AESNI/AESCE. + */ +#include "aesce.h" +#include "aesni.h" +#if defined(MBEDTLS_AESCE_HAVE_CODE) || defined(MBEDTLS_AESNI_HAVE_CODE) +#define HAVE_CONSTANT_TIME_AES +#endif + /* Check the internal consistency of a cipher info structure, and * check it against mbedtls_cipher_info_from_xxx(). */ static int check_cipher_info(mbedtls_cipher_type_t type, @@ -858,6 +872,80 @@ exit: } /* END_CASE */ +/* BEGIN_CASE */ +void decrypt_test_vec_cf(int cipher_id, int pad_mode, data_t *key, + data_t *iv, data_t *cipher, + data_t *clear, data_t *ad, data_t *tag, + int expected_finish_result, int tag_result) +{ + unsigned char output[265]; + mbedtls_cipher_context_t ctx; + size_t outlen, total_len; + + mbedtls_cipher_init(&ctx); + + memset(output, 0x00, sizeof(output)); + +#if !defined(MBEDTLS_GCM_C) && !defined(MBEDTLS_CHACHAPOLY_C) + ((void) ad); + ((void) tag); +#endif + + TEST_CF_SECRET(key->x, key->len); + TEST_CF_SECRET(cipher->x, cipher->len); + + /* Prepare context */ + TEST_ASSERT(0 == mbedtls_cipher_setup(&ctx, + mbedtls_cipher_info_from_type(cipher_id))); + TEST_ASSERT(0 == mbedtls_cipher_setkey(&ctx, key->x, 8 * key->len, MBEDTLS_DECRYPT)); +#if defined(MBEDTLS_CIPHER_MODE_WITH_PADDING) + if (pad_mode != -1) { + TEST_ASSERT(0 == mbedtls_cipher_set_padding_mode(&ctx, pad_mode)); + } +#else + (void) pad_mode; +#endif /* MBEDTLS_CIPHER_MODE_WITH_PADDING */ + TEST_ASSERT(0 == mbedtls_cipher_set_iv(&ctx, iv->x, iv->len)); + TEST_ASSERT(0 == mbedtls_cipher_reset(&ctx)); +#if defined(MBEDTLS_GCM_C) || defined(MBEDTLS_CHACHAPOLY_C) + int expected = (ctx.cipher_info->mode == MBEDTLS_MODE_GCM || + ctx.cipher_info->type == MBEDTLS_CIPHER_CHACHA20_POLY1305) ? + 0 : MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE; + + TEST_EQUAL(expected, mbedtls_cipher_update_ad(&ctx, ad->x, ad->len)); +#endif + + /* decode buffer and check tag->x */ + total_len = 0; + TEST_ASSERT(0 == mbedtls_cipher_update(&ctx, cipher->x, cipher->len, output, &outlen)); + total_len += outlen; + int actual_finish_result = mbedtls_cipher_finish(&ctx, output + outlen, + &outlen); + TEST_EQUAL(actual_finish_result, expected_finish_result); + if (0 != expected_finish_result) { + /* Check output parameter is set to the least-harmful value on error */ + TEST_ASSERT(0 == outlen); + } + total_len += outlen; +#if defined(MBEDTLS_GCM_C) || defined(MBEDTLS_CHACHAPOLY_C) + int tag_expected = (ctx.cipher_info->mode == MBEDTLS_MODE_GCM || + ctx.cipher_info->type == MBEDTLS_CIPHER_CHACHA20_POLY1305) ? + tag_result : MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE; + + TEST_EQUAL(tag_expected, mbedtls_cipher_check_tag(&ctx, tag->x, tag->len)); +#endif + + /* check plaintext only if everything went fine */ + if (0 == expected_finish_result && 0 == tag_result) { + TEST_CF_PUBLIC(output, sizeof(output)); + TEST_MEMORY_COMPARE(output, total_len, clear->x, clear->len); + } + +exit: + mbedtls_cipher_free(&ctx); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_CIPHER_MODE_AEAD */ void auth_crypt_tv(int cipher_id, data_t *key, data_t *iv, data_t *ad, data_t *cipher, data_t *tag, From 2da5328406529ebd057bcbae99c74745cffcdeb6 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 27 Jul 2025 18:44:21 +0200 Subject: [PATCH 45/91] Constant-flow tests for mbedtls_cipher_crypt Add some basic constant-flow tests for `mbedtls_cipher_crypt()`. We already test auxiliary functions and functional behavior pretty thoroughly elsewhere, so here just focus on the interesting cases for constant-flow behavior with this specific function: encrypt, valid decrypt and invalid-padding decrypt. Signed-off-by: Gilles Peskine --- .../test_suite_cipher.constant_time.data | 25 +++++++++ tests/suites/test_suite_cipher.function | 54 +++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/tests/suites/test_suite_cipher.constant_time.data b/tests/suites/test_suite_cipher.constant_time.data index af608044bb..7f9f4af6ff 100644 --- a/tests/suites/test_suite_cipher.constant_time.data +++ b/tests/suites/test_suite_cipher.constant_time.data @@ -120,3 +120,28 @@ decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"0000000000 CF AES-256-CBC Decrypt test vector, PKCS7 (short, bad pad 17) depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"3e536c7917a695485ef046bda7c6a3e6":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-128-CBC crypt Encrypt NIST KAT #4 +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:HAVE_CONSTANT_TIME_AES +test_vec_crypt_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_NONE:MBEDTLS_ENCRYPT:"00000000000000000000000000000000":"00000000000000000000000000000000":"f34481ec3cc627bacd5dc3fb08f273e6":"0336763e966d92595a567cc9ce537f5e":0:0 + +CF AES-128-CBC crypt Decrypt NIST KAT #4 +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:HAVE_CONSTANT_TIME_AES +test_vec_crypt_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_NONE:MBEDTLS_DECRYPT:"00000000000000000000000000000000":"00000000000000000000000000000000":"0336763e966d92595a567cc9ce537f5e":"f34481ec3cc627bacd5dc3fb08f273e6":0:0 + +CF AES-128-CBC crypt Decrypt PKCS7 invalid padding +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +test_vec_crypt_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:MBEDTLS_DECRYPT:"00000000000000000000000000000000":"00000000000000000000000000000000":"0336763e966d92595a567cc9ce537f5e":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-128-CBC crypt Encrypt NIST KAT #4 PSA +depends_on:MBEDTLS_USE_PSA_CRYPTO:MBEDTLS_TEST_DEPRECATED:PSA_WANT_KEY_TYPE_AES:PSA_WANT_ALG_CBC_NO_PADDING:HAVE_CONSTANT_TIME_AES +test_vec_crypt_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_NONE:MBEDTLS_ENCRYPT:"00000000000000000000000000000000":"00000000000000000000000000000000":"f34481ec3cc627bacd5dc3fb08f273e6":"0336763e966d92595a567cc9ce537f5e":0:1 + +CF AES-128-CBC crypt Decrypt NIST KAT #4 PSA +depends_on:MBEDTLS_USE_PSA_CRYPTO:MBEDTLS_TEST_DEPRECATED:PSA_WANT_KEY_TYPE_AES:PSA_WANT_ALG_CBC_NO_PADDING:HAVE_CONSTANT_TIME_AES +test_vec_crypt_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_NONE:MBEDTLS_DECRYPT:"00000000000000000000000000000000":"00000000000000000000000000000000":"0336763e966d92595a567cc9ce537f5e":"f34481ec3cc627bacd5dc3fb08f273e6":0:1 + +## PSA-backed cipher contexts do not support PKCS7 padding. +#CF AES-128-CBC crypt Decrypt PKCS7 invalid padding PSA +#depends_on:MBEDTLS_USE_PSA_CRYPTO:MBEDTLS_TEST_DEPRECATED:PSA_WANT_KEY_TYPE_AES:PSA_WANT_ALG_CBC_PKCS7:HAVE_CONSTANT_TIME_AES +#test_vec_crypt_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:MBEDTLS_DECRYPT:"00000000000000000000000000000000":"00000000000000000000000000000000":"0336763e966d92595a567cc9ce537f5e":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:1 diff --git a/tests/suites/test_suite_cipher.function b/tests/suites/test_suite_cipher.function index 37a77e5687..8ae2234d4c 100644 --- a/tests/suites/test_suite_cipher.function +++ b/tests/suites/test_suite_cipher.function @@ -1228,6 +1228,60 @@ exit: } /* END_CASE */ +/* BEGIN_CASE depends_on:MBEDTLS_CIPHER_MODE_WITH_PADDING */ +void test_vec_crypt_cf(int cipher_id, int pad_mode, int operation, data_t *key, + data_t *iv, data_t *input, data_t *result, + int expected_finish_result, int use_psa) +{ + mbedtls_cipher_context_t ctx; + unsigned char output[32]; + size_t outlen; + + mbedtls_cipher_init(&ctx); + + memset(output, 0x00, sizeof(output)); + + TEST_CF_SECRET(key->x, key->len); + TEST_CF_SECRET(input->x, input->len); + + /* Prepare context */ +#if !defined(MBEDTLS_USE_PSA_CRYPTO) || !defined(MBEDTLS_TEST_DEPRECATED) + (void) use_psa; +#else + if (use_psa == 1) { + PSA_ASSERT(psa_crypto_init()); + TEST_ASSERT(0 == mbedtls_cipher_setup_psa(&ctx, + mbedtls_cipher_info_from_type(cipher_id), 0)); + } else +#endif /* !MBEDTLS_USE_PSA_CRYPTO || !MBEDTLS_TEST_DEPRECATED*/ + TEST_ASSERT(0 == mbedtls_cipher_setup(&ctx, + mbedtls_cipher_info_from_type(cipher_id))); + + TEST_ASSERT(0 == mbedtls_cipher_setkey(&ctx, key->x, 8 * key->len, operation)); + if (MBEDTLS_MODE_CBC == ctx.cipher_info->mode) { + TEST_ASSERT(0 == mbedtls_cipher_set_padding_mode(&ctx, pad_mode)); + } + + int actual_finish_result = + mbedtls_cipher_crypt(&ctx, iv->len ? iv->x : NULL, iv->len, + input->x, input->len, + output, &outlen); + TEST_EQUAL(expected_finish_result, actual_finish_result); + + /* check plaintext only if everything went fine */ + if (0 == expected_finish_result) { + TEST_CF_PUBLIC(output, sizeof(output)); + TEST_MEMORY_COMPARE(output, outlen, result->x, result->len); + } + +exit: + mbedtls_cipher_free(&ctx); +#if defined(MBEDTLS_USE_PSA_CRYPTO) && defined(MBEDTLS_TEST_DEPRECATED) + PSA_DONE(); +#endif /* MBEDTLS_USE_PSA_CRYPTO && MBEDTLS_TEST_DEPRECATED */ +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_CIPHER_MODE_WITH_PADDING */ void set_padding(int cipher_id, int pad_mode, int ret) { From 155de2ab775e77ab6fa81bf2b1e6e63768123bc1 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 27 Jul 2025 18:53:57 +0200 Subject: [PATCH 46/91] New function mbedtls_cipher_finish_padded New function `mbedtls_cipher_finish_padded()`, similar to `mbedtls_cipher_finish()`, but reporting padding errors through a separate output parameter. This makes it easier to avoid leaking the presence of a padding error, especially through timing. Thus the new function is recommended to defend against padding oracle attacks. In this commit, implement this function naively, with timing that depends on whether an error happened. A subsequent commit will make this function constant-time. Copy the test decrypt_test_vec and decrypt_test_vec_cf test cases into variants that call `mbedtls_cipher_finish_padded()`. Signed-off-by: Gilles Peskine --- include/mbedtls/cipher.h | 66 ++++++- library/cipher.c | 17 ++ tests/suites/test_suite_cipher.aes.data | 108 +++++++++++ .../test_suite_cipher.constant_time.data | 108 +++++++++++ tests/suites/test_suite_cipher.function | 173 ++++++++++++++++++ 5 files changed, 468 insertions(+), 4 deletions(-) diff --git a/include/mbedtls/cipher.h b/include/mbedtls/cipher.h index 1dc31c9c24..6ef703ddab 100644 --- a/include/mbedtls/cipher.h +++ b/include/mbedtls/cipher.h @@ -881,7 +881,7 @@ int mbedtls_cipher_set_iv(mbedtls_cipher_context_t *ctx, * 1. mbedtls_cipher_set_iv() if the mode uses an IV/nonce. * 2. mbedtls_cipher_reset() * 3. mbedtls_cipher_update() one or more times - * 4. mbedtls_cipher_finish() + * 4. mbedtls_cipher_finish() or mbedtls_cipher_finish_padded() * . * This sequence can be repeated to encrypt or decrypt multiple * messages with the same key. @@ -892,7 +892,7 @@ int mbedtls_cipher_set_iv(mbedtls_cipher_context_t *ctx, * 2. mbedtls_cipher_reset() * 3. mbedtls_cipher_update_ad() * 4. mbedtls_cipher_update() one or more times - * 5. mbedtls_cipher_finish() + * 5. mbedtls_cipher_finish() or mbedtls_cipher_finish_padded() * 6. mbedtls_cipher_check_tag() (for decryption) or * mbedtls_cipher_write_tag() (for encryption). * . @@ -930,7 +930,8 @@ int mbedtls_cipher_update_ad(mbedtls_cipher_context_t *ctx, * many block-sized blocks of data as possible to output. * Any data that cannot be written immediately is either * added to the next block, or flushed when - * mbedtls_cipher_finish() is called. + * mbedtls_cipher_finish() or mbedtls_cipher_finish_padded() + * is called. * Exception: For MBEDTLS_MODE_ECB, expects a single block * in size. For example, 16 Bytes for AES. * @@ -964,6 +965,19 @@ int mbedtls_cipher_update(mbedtls_cipher_context_t *ctx, * contained in it is padded to the size of * the last block, and written to the \p output buffer. * + * \warning This function reports invalid padding through an error + * code. Adversaries may be able to decrypt encrypted + * data if they can submit chosen ciphertexts and + * detect whether it has valid padding or not, + * either through direct observation or through a side + * channel such as timing. This is known as a + * padding oracle attack. + * Therefore applications that call this function for + * decryption with a cipher that involves padding + * should take care around error handling. Preferably, + * such applicatios should use + * mbedtls_cipher_finish_padded() instead of this function. + * * \param ctx The generic cipher context. This must be initialized and * bound to a key. * \param output The buffer to write data to. This needs to be a writable @@ -977,12 +991,56 @@ int mbedtls_cipher_update(mbedtls_cipher_context_t *ctx, * \return #MBEDTLS_ERR_CIPHER_FULL_BLOCK_EXPECTED on decryption * expecting a full block but not receiving one. * \return #MBEDTLS_ERR_CIPHER_INVALID_PADDING on invalid padding - * while decrypting. + * while decrypting. Note that invalid-padding errors + * should be handled carefully; see the warning above. * \return A cipher-specific error code on failure. */ int mbedtls_cipher_finish(mbedtls_cipher_context_t *ctx, unsigned char *output, size_t *olen); +/** + * \brief The generic cipher finalization function. If data still + * needs to be flushed from an incomplete block, the data + * contained in it is padded to the size of + * the last block, and written to the \p output buffer. + * + * \note This function is similar to mbedtls_cipher_finish(). + * The only difference is that it reports invalid padding + * decryption differently, through the \p invalid_padding + * parameter rather than an error code. + * For encryption, and in modes without padding (including + * all authenticated modes), this function is identical + * to mbedtls_cipher_finish(). + * + * \param[in,out] ctx The generic cipher context. This must be initialized and + * bound to a key. + * \param[out] output The buffer to write data to. This needs to be a writable + * buffer of at least block_size Bytes. + * \param[out] olen The length of the data written to the \p output buffer. + * This may not be \c NULL. + * \param[out] invalid_padding + * If this function returns \c 0 on decryption, + * \p *invalid_padding is \c 0 if the ciphertext was + * valid, and all-bits-one if the ciphertext had invalid + * padding. + * On encryption, or in a mode without padding (including + * all authenticated modes), \p *invalid_padding is \c 0 + * on success. + * The value in \p *invalid_padding is unspecified if + * this function returns a nonzero status. + * + * \return \c 0 on success. + * Also \c 0 for decryption with invalid padding. + * \return #MBEDTLS_ERR_CIPHER_BAD_INPUT_DATA on + * parameter-verification failure. + * \return #MBEDTLS_ERR_CIPHER_FULL_BLOCK_EXPECTED on decryption + * expecting a full block but not receiving one. + * \return A cipher-specific error code on failure. + */ +int mbedtls_cipher_finish_padded(mbedtls_cipher_context_t *ctx, + unsigned char *output, size_t *olen, + size_t *invalid_padding); + #if defined(MBEDTLS_GCM_C) || defined(MBEDTLS_CHACHAPOLY_C) /** * \brief This function writes a tag for AEAD ciphers. diff --git a/library/cipher.c b/library/cipher.c index 2ae01dd84d..f3e2f91f82 100644 --- a/library/cipher.c +++ b/library/cipher.c @@ -1124,6 +1124,23 @@ int mbedtls_cipher_finish(mbedtls_cipher_context_t *ctx, return MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE; } +int mbedtls_cipher_finish_padded(mbedtls_cipher_context_t *ctx, + unsigned char *output, size_t *olen, + size_t *invalid_padding) +{ + *invalid_padding = 0; + int ret = mbedtls_cipher_finish(ctx, output, olen); +#if defined(MBEDTLS_CIPHER_PADDING_PKCS7) || \ + defined(MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS) || \ + defined(MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN) + if (ret == MBEDTLS_ERR_CIPHER_INVALID_PADDING) { + ret = 0; + *invalid_padding = SIZE_MAX; + } +#endif + return ret; +} + #if defined(MBEDTLS_CIPHER_MODE_WITH_PADDING) int mbedtls_cipher_set_padding_mode(mbedtls_cipher_context_t *ctx, mbedtls_cipher_padding_t mode) diff --git a/tests/suites/test_suite_cipher.aes.data b/tests/suites/test_suite_cipher.aes.data index dd8bf719e0..4298b1d595 100644 --- a/tests/suites/test_suite_cipher.aes.data +++ b/tests/suites/test_suite_cipher.aes.data @@ -1590,6 +1590,114 @@ AES-256-CBC Decrypt test vector, PKCS7 (short, bad pad 17) depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 decrypt_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"3e536c7917a695485ef046bda7c6a3e6":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 +AES-128-CBC Decrypt with finish_padded, no padding +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_NONE:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"23f710842b9bb9c32f26648c786807ca":"00000000000000000000000000000000":"":"":0:0 + +AES-192-CBC Decrypt with finish_padded, no padding +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_NONE:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"707b1dbb0ffa40ef7d95def421233fae":"fffffffff80000000000000000000000":"":"":0:0 + +AES-256-CBC Decrypt with finish_padded, no padding +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_NONE:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"49af6b372135acef10132e548f217b17":"ff000000000000000000000000000000":"":"":0:0 + +AES-128-CBC Decrypt with finish_padded, PKCS7 (good pad 1) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"6dbd04d1579f6a7bee0842b9ae491588":"000000000000000000000000000000":"":"":0:0 + +AES-192-CBC Decrypt with finish_padded, PKCS7 (good pad 1) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"593ebdf9a785f414cbed5d8a9eee1e4d":"fffffffff800000000000000000000":"":"":0:0 + +AES-256-CBC Decrypt with finish_padded, PKCS7 (good pad 1) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"f1b27ac78b93f6b0ab9787d8827176e6":"ff0000000000000000000000000000":"":"":0:0 + +AES-128-CBC Decrypt with finish_padded, PKCS7 (good pad 2) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"175334ced3166a22437861f4bcced178":"0000000000000000000000000000":"":"":0:0 + +AES-192-CBC Decrypt with finish_padded, PKCS7 (good pad 2) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"5d9ee7bc7066e438582c86c165604f2e":"fffffffff8000000000000000000":"":"":0:0 + +AES-256-CBC Decrypt with finish_padded, PKCS7 (good pad 2) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"cc71abea78b8e82c3791b52d3dba55e2":"ff00000000000000000000000000":"":"":0:0 + +AES-128-CBC Decrypt with finish_padded, PKCS7 (good pad 15) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"7d340c60b9067883962c69766cf9ec35":"2a":"":"":0:0 + +AES-192-CBC Decrypt with finish_padded, PKCS7 (good pad 15) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"48fef8aaa78c4a148f241aaf14866772":"2a":"":"":0:0 + +AES-256-CBC Decrypt with finish_padded, PKCS7 (good pad 15) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"6ad50ca266a3e32024818f11839afb2f":"2a":"":"":0:0 + +AES-128-CBC Decrypt with finish_padded, PKCS7 (good pad 16) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"e9844992c4b55bcaf8199d5df842adad":"":"":"":0:0 + +AES-192-CBC Decrypt with finish_padded, PKCS7 (good pad 16) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"02bb292527e726fd51eb29894d6f0aad":"":"":"":0:0 + +AES-256-CBC Decrypt with finish_padded, PKCS7 (good pad 16) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"1f788fe6d86c317549697fbf0c07fa43":"":"":"":0:0 + +AES-128-CBC Decrypt with finish_padded, PKCS7 (bad pad 0) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"23f710842b9bb9c32f26648c786807ca":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-192-CBC Decrypt with finish_padded, PKCS7 (bad pad 0) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"707b1dbb0ffa40ef7d95def421233fae":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-256-CBC Decrypt with finish_padded, PKCS7 (bad pad 0) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"49af6b372135acef10132e548f217b17":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-128-CBC Decrypt with finish_padded, PKCS7 (bad pad 0102) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"e651289760d35177eade56eae724f8fd":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-192-CBC Decrypt with finish_padded, PKCS7 (bad pad 0102) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"f5b599490354e71a3b3fb5f1419fb971":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-256-CBC Decrypt with finish_padded, PKCS7 (bad pad 0102) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"6852d318a0884a289a725c558e761e25":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-128-CBC Decrypt with finish_padded, PKCS7 (long, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"9c336551cc31074ffcefc161bac686b7afa572c3b53bd14a1b98d201229ddd03":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-192-CBC Decrypt with finish_padded, PKCS7 (long, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"33a31ec7605c85893872a467777f3ddb5c4271870e51a0c618f20a0efccc2bfc":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-256-CBC Decrypt with finish_padded, PKCS7 (long, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"3e536c7917a695485ef046bda7c6a3e64f439d87aa0deb338029253bea0ba54f":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-128-CBC Decrypt with finish_padded, PKCS7 (short, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"9c336551cc31074ffcefc161bac686b7":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-192-CBC Decrypt with finish_padded, PKCS7 (short, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"33a31ec7605c85893872a467777f3ddb":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +AES-256-CBC Decrypt with finish_padded, PKCS7 (short, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7 +decrypt_padded_test_vec:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"3e536c7917a695485ef046bda7c6a3e6":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + AES-128-CFB Decrypt test vector depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CFB decrypt_test_vec:MBEDTLS_CIPHER_AES_128_CFB128:-1:"fffffffe000000000000000000000000":"00000000000000000000000000000000":"1114bc2028009b923f0b01915ce5e7c4":"00000000000000000000000000000000":"":"":0:0: diff --git a/tests/suites/test_suite_cipher.constant_time.data b/tests/suites/test_suite_cipher.constant_time.data index 7f9f4af6ff..2de31ed15c 100644 --- a/tests/suites/test_suite_cipher.constant_time.data +++ b/tests/suites/test_suite_cipher.constant_time.data @@ -13,6 +13,114 @@ get_pkcs_padding:"00112233445566778899AABBCCDDEE00":MBEDTLS_ERR_CIPHER_INVALID_P Constant-time PKCS7 padding, invalid > 16 get_pkcs_padding:"00112233445566778899AABBCCDDEE11":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 +CF AES-128-CBC Decrypt with finish_padded, no padding +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_NONE:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"23f710842b9bb9c32f26648c786807ca":"00000000000000000000000000000000":"":"":0:0 + +CF AES-192-CBC Decrypt with finish_padded, no padding +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_NONE:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"707b1dbb0ffa40ef7d95def421233fae":"fffffffff80000000000000000000000":"":"":0:0 + +CF AES-256-CBC Decrypt with finish_padded, no padding +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_NONE:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"49af6b372135acef10132e548f217b17":"ff000000000000000000000000000000":"":"":0:0 + +CF AES-128-CBC Decrypt with finish_padded, PKCS7 (good pad 1) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"6dbd04d1579f6a7bee0842b9ae491588":"000000000000000000000000000000":"":"":0:0 + +CF AES-192-CBC Decrypt with finish_padded, PKCS7 (good pad 1) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"593ebdf9a785f414cbed5d8a9eee1e4d":"fffffffff800000000000000000000":"":"":0:0 + +CF AES-256-CBC Decrypt with finish_padded, PKCS7 (good pad 1) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"f1b27ac78b93f6b0ab9787d8827176e6":"ff0000000000000000000000000000":"":"":0:0 + +CF AES-128-CBC Decrypt with finish_padded, PKCS7 (good pad 2) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"175334ced3166a22437861f4bcced178":"0000000000000000000000000000":"":"":0:0 + +CF AES-192-CBC Decrypt with finish_padded, PKCS7 (good pad 2) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"5d9ee7bc7066e438582c86c165604f2e":"fffffffff8000000000000000000":"":"":0:0 + +CF AES-256-CBC Decrypt with finish_padded, PKCS7 (good pad 2) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"cc71abea78b8e82c3791b52d3dba55e2":"ff00000000000000000000000000":"":"":0:0 + +CF AES-128-CBC Decrypt with finish_padded, PKCS7 (good pad 15) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"7d340c60b9067883962c69766cf9ec35":"2a":"":"":0:0 + +CF AES-192-CBC Decrypt with finish_padded, PKCS7 (good pad 15) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"48fef8aaa78c4a148f241aaf14866772":"2a":"":"":0:0 + +CF AES-256-CBC Decrypt with finish_padded, PKCS7 (good pad 15) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"6ad50ca266a3e32024818f11839afb2f":"2a":"":"":0:0 + +CF AES-128-CBC Decrypt with finish_padded, PKCS7 (good pad 16) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"e9844992c4b55bcaf8199d5df842adad":"":"":"":0:0 + +CF AES-192-CBC Decrypt with finish_padded, PKCS7 (good pad 16) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"02bb292527e726fd51eb29894d6f0aad":"":"":"":0:0 + +CF AES-256-CBC Decrypt with finish_padded, PKCS7 (good pad 16) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"1f788fe6d86c317549697fbf0c07fa43":"":"":"":0:0 + +CF AES-128-CBC Decrypt with finish_padded, PKCS7 (bad pad 0) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"23f710842b9bb9c32f26648c786807ca":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-192-CBC Decrypt with finish_padded, PKCS7 (bad pad 0) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"707b1dbb0ffa40ef7d95def421233fae":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-256-CBC Decrypt with finish_padded, PKCS7 (bad pad 0) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"49af6b372135acef10132e548f217b17":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-128-CBC Decrypt with finish_padded, PKCS7 (bad pad 0102) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"e651289760d35177eade56eae724f8fd":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-192-CBC Decrypt with finish_padded, PKCS7 (bad pad 0102) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"f5b599490354e71a3b3fb5f1419fb971":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-256-CBC Decrypt with finish_padded, PKCS7 (bad pad 0102) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"6852d318a0884a289a725c558e761e25":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-128-CBC Decrypt with finish_padded, PKCS7 (long, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"9c336551cc31074ffcefc161bac686b7afa572c3b53bd14a1b98d201229ddd03":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-192-CBC Decrypt with finish_padded, PKCS7 (long, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"33a31ec7605c85893872a467777f3ddb5c4271870e51a0c618f20a0efccc2bfc":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-256-CBC Decrypt with finish_padded, PKCS7 (long, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"3e536c7917a695485ef046bda7c6a3e64f439d87aa0deb338029253bea0ba54f":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-128-CBC Decrypt with finish_padded, PKCS7 (short, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_PKCS7:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"9c336551cc31074ffcefc161bac686b7":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-192-CBC Decrypt with finish_padded, PKCS7 (short, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_192_CBC:MBEDTLS_PADDING_PKCS7:"000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"33a31ec7605c85893872a467777f3ddb":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + +CF AES-256-CBC Decrypt with finish_padded, PKCS7 (short, bad pad 17) +depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_CIPHER_PADDING_PKCS7:HAVE_CONSTANT_TIME_AES +decrypt_padded_test_vec_cf:MBEDTLS_CIPHER_AES_256_CBC:MBEDTLS_PADDING_PKCS7:"0000000000000000000000000000000000000000000000000000000000000000":"00000000000000000000000000000000":"3e536c7917a695485ef046bda7c6a3e6":"":"":"":MBEDTLS_ERR_CIPHER_INVALID_PADDING:0 + CF AES-128-CBC Decrypt test vector, no padding depends_on:MBEDTLS_AES_C:MBEDTLS_CIPHER_MODE_CBC:HAVE_CONSTANT_TIME_AES decrypt_test_vec_cf:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_PADDING_NONE:"ffffffffe00000000000000000000000":"00000000000000000000000000000000":"23f710842b9bb9c32f26648c786807ca":"00000000000000000000000000000000":"":"":0:0 diff --git a/tests/suites/test_suite_cipher.function b/tests/suites/test_suite_cipher.function index 8ae2234d4c..fbc48b7974 100644 --- a/tests/suites/test_suite_cipher.function +++ b/tests/suites/test_suite_cipher.function @@ -946,6 +946,179 @@ exit: } /* END_CASE */ +/* BEGIN_CASE */ +void decrypt_padded_test_vec(int cipher_id, int pad_mode, data_t *key, + data_t *iv, data_t *cipher, + data_t *clear, data_t *ad, data_t *tag, + int expected_finish_result, int tag_result) +{ + unsigned char output[265]; + mbedtls_cipher_context_t ctx; + size_t outlen, total_len; + + mbedtls_cipher_init(&ctx); + + memset(output, 0x00, sizeof(output)); + +#if !defined(MBEDTLS_GCM_C) && !defined(MBEDTLS_CHACHAPOLY_C) + ((void) ad); + ((void) tag); +#endif + + /* Prepare context */ + TEST_ASSERT(0 == mbedtls_cipher_setup(&ctx, + mbedtls_cipher_info_from_type(cipher_id))); + TEST_ASSERT(0 == mbedtls_cipher_setkey(&ctx, key->x, 8 * key->len, MBEDTLS_DECRYPT)); +#if defined(MBEDTLS_CIPHER_MODE_WITH_PADDING) + if (pad_mode != -1) { + TEST_ASSERT(0 == mbedtls_cipher_set_padding_mode(&ctx, pad_mode)); + } +#else + (void) pad_mode; +#endif /* MBEDTLS_CIPHER_MODE_WITH_PADDING */ + TEST_ASSERT(0 == mbedtls_cipher_set_iv(&ctx, iv->x, iv->len)); + TEST_ASSERT(0 == mbedtls_cipher_reset(&ctx)); +#if defined(MBEDTLS_GCM_C) || defined(MBEDTLS_CHACHAPOLY_C) + int expected = (ctx.cipher_info->mode == MBEDTLS_MODE_GCM || + ctx.cipher_info->type == MBEDTLS_CIPHER_CHACHA20_POLY1305) ? + 0 : MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE; + + TEST_EQUAL(expected, mbedtls_cipher_update_ad(&ctx, ad->x, ad->len)); +#endif + + /* decode buffer and check tag->x */ + total_len = 0; + TEST_ASSERT(0 == mbedtls_cipher_update(&ctx, cipher->x, cipher->len, output, &outlen)); + total_len += outlen; + + size_t invalid_padding = 42; + int actual_finish_result = + mbedtls_cipher_finish_padded(&ctx, output + outlen, &outlen, + &invalid_padding); + switch (expected_finish_result) { + case 0: + TEST_EQUAL(actual_finish_result, 0); + TEST_EQUAL(invalid_padding, 0); + break; + case MBEDTLS_ERR_CIPHER_INVALID_PADDING: + TEST_EQUAL(actual_finish_result, 0); + TEST_EQUAL(invalid_padding, SIZE_MAX); + break; + default: + TEST_EQUAL(actual_finish_result, expected_finish_result); + /* Check output parameter is set to the least-harmful value on error */ + TEST_EQUAL(0, outlen); + break; + } + total_len += outlen; + +#if defined(MBEDTLS_GCM_C) || defined(MBEDTLS_CHACHAPOLY_C) + int tag_expected = (ctx.cipher_info->mode == MBEDTLS_MODE_GCM || + ctx.cipher_info->type == MBEDTLS_CIPHER_CHACHA20_POLY1305) ? + tag_result : MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE; + + TEST_EQUAL(tag_expected, mbedtls_cipher_check_tag(&ctx, tag->x, tag->len)); +#endif + + /* check plaintext only if everything went fine */ + if (0 == expected_finish_result && 0 == tag_result) { + TEST_ASSERT(total_len == clear->len); + TEST_ASSERT(0 == memcmp(output, clear->x, clear->len)); + } + +exit: + mbedtls_cipher_free(&ctx); +} +/* END_CASE */ + +/* BEGIN_CASE */ +void decrypt_padded_test_vec_cf(int cipher_id, int pad_mode, data_t *key, + data_t *iv, data_t *cipher, + data_t *clear, data_t *ad, data_t *tag, + int expected_finish_result, int tag_result) +{ + unsigned char output[265]; + mbedtls_cipher_context_t ctx; + size_t outlen, total_len; + + mbedtls_cipher_init(&ctx); + + memset(output, 0x00, sizeof(output)); + +#if !defined(MBEDTLS_GCM_C) && !defined(MBEDTLS_CHACHAPOLY_C) + ((void) ad); + ((void) tag); +#endif + + TEST_CF_SECRET(key->x, key->len); + TEST_CF_SECRET(cipher->x, cipher->len); + + /* Prepare context */ + TEST_ASSERT(0 == mbedtls_cipher_setup(&ctx, + mbedtls_cipher_info_from_type(cipher_id))); + TEST_ASSERT(0 == mbedtls_cipher_setkey(&ctx, key->x, 8 * key->len, MBEDTLS_DECRYPT)); +#if defined(MBEDTLS_CIPHER_MODE_WITH_PADDING) + if (pad_mode != -1) { + TEST_ASSERT(0 == mbedtls_cipher_set_padding_mode(&ctx, pad_mode)); + } +#else + (void) pad_mode; +#endif /* MBEDTLS_CIPHER_MODE_WITH_PADDING */ + TEST_ASSERT(0 == mbedtls_cipher_set_iv(&ctx, iv->x, iv->len)); + TEST_ASSERT(0 == mbedtls_cipher_reset(&ctx)); +#if defined(MBEDTLS_GCM_C) || defined(MBEDTLS_CHACHAPOLY_C) + int expected = (ctx.cipher_info->mode == MBEDTLS_MODE_GCM || + ctx.cipher_info->type == MBEDTLS_CIPHER_CHACHA20_POLY1305) ? + 0 : MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE; + + TEST_EQUAL(expected, mbedtls_cipher_update_ad(&ctx, ad->x, ad->len)); +#endif + + /* decode buffer and check tag->x */ + total_len = 0; + TEST_ASSERT(0 == mbedtls_cipher_update(&ctx, cipher->x, cipher->len, output, &outlen)); + total_len += outlen; + + size_t invalid_padding = 42; + int actual_finish_result = + mbedtls_cipher_finish_padded(&ctx, output + outlen, &outlen, + &invalid_padding); + switch (expected_finish_result) { + case 0: + TEST_EQUAL(actual_finish_result, 0); + TEST_EQUAL(invalid_padding, 0); + break; + case MBEDTLS_ERR_CIPHER_INVALID_PADDING: + TEST_EQUAL(actual_finish_result, 0); + TEST_EQUAL(invalid_padding, SIZE_MAX); + break; + default: + TEST_EQUAL(actual_finish_result, expected_finish_result); + /* Check output parameter is set to the least-harmful value on error */ + TEST_EQUAL(0, outlen); + break; + } + total_len += outlen; + +#if defined(MBEDTLS_GCM_C) || defined(MBEDTLS_CHACHAPOLY_C) + int tag_expected = (ctx.cipher_info->mode == MBEDTLS_MODE_GCM || + ctx.cipher_info->type == MBEDTLS_CIPHER_CHACHA20_POLY1305) ? + tag_result : MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE; + + TEST_EQUAL(tag_expected, mbedtls_cipher_check_tag(&ctx, tag->x, tag->len)); +#endif + + /* check plaintext only if everything went fine */ + if (0 == expected_finish_result && 0 == tag_result) { + TEST_CF_PUBLIC(output, sizeof(output)); + TEST_MEMORY_COMPARE(output, total_len, clear->x, clear->len); + } + +exit: + mbedtls_cipher_free(&ctx); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_CIPHER_MODE_AEAD */ void auth_crypt_tv(int cipher_id, data_t *key, data_t *iv, data_t *ad, data_t *cipher, data_t *tag, From 6cb9f35d8c84d6b3d92e2071a31fb6b183da949f Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 27 Jul 2025 21:22:39 +0200 Subject: [PATCH 47/91] Switch legacy cipher to constant-time invalid padding reporting In internal `get_padding` functions, report whether the padding was invalid through a separate output parameter, rather than the return code. Take advantage of this to have `mbedtls_cipher_finish_padded()` be the easy path that just passes the `invalid_padding` through. Make `mbedtls_cipher_finish()` a wrapper around `mbedtls_cipher_finish_padded()` that converts the invalid-padding output into an error code. Signed-off-by: Gilles Peskine --- include/mbedtls/cipher.h | 11 ++++- library/cipher.c | 62 ++++++++++++++----------- library/cipher_invasive.h | 3 +- tests/suites/test_suite_cipher.function | 43 +++++++++++++---- 4 files changed, 78 insertions(+), 41 deletions(-) diff --git a/include/mbedtls/cipher.h b/include/mbedtls/cipher.h index 6ef703ddab..3778f44551 100644 --- a/include/mbedtls/cipher.h +++ b/include/mbedtls/cipher.h @@ -329,8 +329,15 @@ typedef struct mbedtls_cipher_context_t { /** Padding functions to use, if relevant for * the specific cipher mode. */ - void(*MBEDTLS_PRIVATE(add_padding))(unsigned char *output, size_t olen, size_t data_len); - int(*MBEDTLS_PRIVATE(get_padding))(unsigned char *input, size_t ilen, size_t *data_len); + void(*MBEDTLS_PRIVATE(add_padding))(unsigned char *output, size_t olen, + size_t data_len); + /* Report invalid-padding condition through the output parameter + * invalid_padding. To minimize changes in Mbed TLS 3.6, where this + * declaration is in a public header, use the public type size_t + * rather than the internal type mbedtls_ct_condition_t. */ + int(*MBEDTLS_PRIVATE(get_padding))(unsigned char *input, size_t ilen, + size_t *data_len, + size_t *invalid_padding); #endif /** Buffer for input that has not been processed yet. */ diff --git a/library/cipher.c b/library/cipher.c index f3e2f91f82..f9d46213db 100644 --- a/library/cipher.c +++ b/library/cipher.c @@ -846,7 +846,8 @@ static void add_pkcs_padding(unsigned char *output, size_t output_len, */ MBEDTLS_STATIC_TESTABLE int mbedtls_get_pkcs_padding(unsigned char *input, size_t input_len, - size_t *data_len) + size_t *data_len, + size_t *invalid_padding) { size_t i, pad_idx; unsigned char padding_len; @@ -872,7 +873,8 @@ MBEDTLS_STATIC_TESTABLE int mbedtls_get_pkcs_padding(unsigned char *input, /* If the padding is invalid, set the output length to 0 */ *data_len = mbedtls_ct_if(bad, 0, input_len - padding_len); - return mbedtls_ct_error_if_else_0(bad, MBEDTLS_ERR_CIPHER_INVALID_PADDING); + *invalid_padding = mbedtls_ct_size_if_else_0(bad, SIZE_MAX); + return 0; } #endif /* MBEDTLS_CIPHER_PADDING_PKCS7 */ @@ -893,7 +895,7 @@ static void add_one_and_zeros_padding(unsigned char *output, } static int get_one_and_zeros_padding(unsigned char *input, size_t input_len, - size_t *data_len) + size_t *data_len, size_t *invalid_padding) { if (NULL == input || NULL == data_len) { return MBEDTLS_ERR_CIPHER_BAD_INPUT_DATA; @@ -916,7 +918,8 @@ static int get_one_and_zeros_padding(unsigned char *input, size_t input_len, in_padding = mbedtls_ct_bool_and(in_padding, mbedtls_ct_bool_not(is_nonzero)); } - return mbedtls_ct_error_if_else_0(bad, MBEDTLS_ERR_CIPHER_INVALID_PADDING); + *invalid_padding = mbedtls_ct_size_if_else_0(bad, SIZE_MAX); + return 0; } #endif /* MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS */ @@ -937,7 +940,7 @@ static void add_zeros_and_len_padding(unsigned char *output, } static int get_zeros_and_len_padding(unsigned char *input, size_t input_len, - size_t *data_len) + size_t *data_len, size_t *invalid_padding) { size_t i, pad_idx; unsigned char padding_len; @@ -963,7 +966,8 @@ static int get_zeros_and_len_padding(unsigned char *input, size_t input_len, bad = mbedtls_ct_bool_or(bad, nonzero_pad_byte); } - return mbedtls_ct_error_if_else_0(bad, MBEDTLS_ERR_CIPHER_INVALID_PADDING); + *invalid_padding = mbedtls_ct_size_if_else_0(bad, SIZE_MAX); + return 0; } #endif /* MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN */ @@ -978,7 +982,7 @@ static void add_zeros_padding(unsigned char *output, } static int get_zeros_padding(unsigned char *input, size_t input_len, - size_t *data_len) + size_t *data_len, size_t *invalid_padding) { size_t i; mbedtls_ct_condition_t done = MBEDTLS_CT_FALSE, prev_done; @@ -994,6 +998,7 @@ static int get_zeros_padding(unsigned char *input, size_t input_len, *data_len = mbedtls_ct_size_if(mbedtls_ct_bool_ne(done, prev_done), i, *data_len); } + *invalid_padding = 0; return 0; } #endif /* MBEDTLS_CIPHER_PADDING_ZEROS */ @@ -1005,20 +1010,21 @@ static int get_zeros_padding(unsigned char *input, size_t input_len, * but a trivial get_padding function */ static int get_no_padding(unsigned char *input, size_t input_len, - size_t *data_len) + size_t *data_len, size_t *invalid_padding) { if (NULL == input || NULL == data_len) { return MBEDTLS_ERR_CIPHER_BAD_INPUT_DATA; } *data_len = input_len; - + *invalid_padding = 0; return 0; } #endif /* MBEDTLS_CIPHER_MODE_WITH_PADDING */ -int mbedtls_cipher_finish(mbedtls_cipher_context_t *ctx, - unsigned char *output, size_t *olen) +int mbedtls_cipher_finish_padded(mbedtls_cipher_context_t *ctx, + unsigned char *output, size_t *olen, + size_t *invalid_padding) { if (ctx->cipher_info == NULL) { return MBEDTLS_ERR_CIPHER_BAD_INPUT_DATA; @@ -1034,6 +1040,7 @@ int mbedtls_cipher_finish(mbedtls_cipher_context_t *ctx, #endif /* MBEDTLS_USE_PSA_CRYPTO && !MBEDTLS_DEPRECATED_REMOVED */ *olen = 0; + *invalid_padding = 0; #if defined(MBEDTLS_CIPHER_MODE_WITH_PADDING) /* CBC mode requires padding so we make sure a call to @@ -1110,7 +1117,7 @@ int mbedtls_cipher_finish(mbedtls_cipher_context_t *ctx, /* Set output size for decryption */ if (MBEDTLS_DECRYPT == ctx->operation) { return ctx->get_padding(output, mbedtls_cipher_get_block_size(ctx), - olen); + olen, invalid_padding); } /* Set output size for encryption */ @@ -1124,20 +1131,16 @@ int mbedtls_cipher_finish(mbedtls_cipher_context_t *ctx, return MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE; } -int mbedtls_cipher_finish_padded(mbedtls_cipher_context_t *ctx, - unsigned char *output, size_t *olen, - size_t *invalid_padding) +int mbedtls_cipher_finish(mbedtls_cipher_context_t *ctx, + unsigned char *output, size_t *olen) { - *invalid_padding = 0; - int ret = mbedtls_cipher_finish(ctx, output, olen); -#if defined(MBEDTLS_CIPHER_PADDING_PKCS7) || \ - defined(MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS) || \ - defined(MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN) - if (ret == MBEDTLS_ERR_CIPHER_INVALID_PADDING) { - ret = 0; - *invalid_padding = SIZE_MAX; + size_t invalid_padding = 0; + int ret = mbedtls_cipher_finish_padded(ctx, output, olen, + &invalid_padding); + if (ret == 0) { + ret = mbedtls_ct_error_if_else_0(invalid_padding, + MBEDTLS_ERR_CIPHER_INVALID_PADDING); } -#endif return ret; } @@ -1410,14 +1413,17 @@ int mbedtls_cipher_crypt(mbedtls_cipher_context_t *ctx, return ret; } - if ((ret = mbedtls_cipher_finish(ctx, output + *olen, - &finish_olen)) != 0) { + size_t invalid_padding = 0; + if ((ret = mbedtls_cipher_finish_padded(ctx, output + *olen, + &finish_olen, + &invalid_padding)) != 0) { return ret; } - *olen += finish_olen; - return 0; + ret = mbedtls_ct_error_if_else_0(invalid_padding, + MBEDTLS_ERR_CIPHER_INVALID_PADDING); + return ret; } #if defined(MBEDTLS_CIPHER_MODE_AEAD) diff --git a/library/cipher_invasive.h b/library/cipher_invasive.h index 702f8f73e9..e82a0a7f99 100644 --- a/library/cipher_invasive.h +++ b/library/cipher_invasive.h @@ -20,7 +20,8 @@ MBEDTLS_STATIC_TESTABLE int mbedtls_get_pkcs_padding(unsigned char *input, size_t input_len, - size_t *data_len); + size_t *data_len, + size_t *invalid_padding); #endif diff --git a/tests/suites/test_suite_cipher.function b/tests/suites/test_suite_cipher.function index fbc48b7974..9e3e026834 100644 --- a/tests/suites/test_suite_cipher.function +++ b/tests/suites/test_suite_cipher.function @@ -1475,8 +1475,8 @@ exit: /* END_CASE */ /* BEGIN_CASE depends_on:MBEDTLS_CIPHER_MODE_CBC */ -void check_padding(int pad_mode, data_t *input, int ret, int dlen_check - ) +void check_padding(int pad_mode, data_t *input, + int expected_ret, int dlen_check) { mbedtls_cipher_info_t cipher_info; mbedtls_cipher_context_t ctx; @@ -1489,10 +1489,20 @@ void check_padding(int pad_mode, data_t *input, int ret, int dlen_check TEST_ASSERT(0 == mbedtls_cipher_set_padding_mode(&ctx, pad_mode)); - - TEST_ASSERT(ret == ctx.get_padding(input->x, input->len, &dlen)); - if (0 == ret) { - TEST_ASSERT(dlen == (size_t) dlen_check); + size_t invalid_padding = 42; + int ret = ctx.get_padding(input->x, input->len, &dlen, &invalid_padding); + switch (expected_ret) { + case 0: + TEST_EQUAL(ret, 0); + TEST_EQUAL(invalid_padding, 0); + TEST_EQUAL(dlen, dlen_check); + break; + case MBEDTLS_ERR_CIPHER_INVALID_PADDING: + TEST_EQUAL(ret, 0); + TEST_EQUAL(invalid_padding, SIZE_MAX); + break; + default: + TEST_EQUAL(ret, expected_ret); } } /* END_CASE */ @@ -1585,14 +1595,27 @@ void get_pkcs_padding(data_t *decrypted_block, int exp_ret, int exp_len) { int ret; size_t calculated_len; + size_t invalid_padding; TEST_CF_SECRET(decrypted_block->x, decrypted_block->len); ret = mbedtls_get_pkcs_padding(decrypted_block->x, decrypted_block->len, - &calculated_len); + &calculated_len, &invalid_padding); - TEST_EQUAL(ret, exp_ret); - if (exp_ret == 0) { - TEST_EQUAL(calculated_len, exp_len); + switch (exp_ret) { + case 0: + TEST_EQUAL(ret, 0); + TEST_EQUAL(invalid_padding, 0); + TEST_EQUAL(calculated_len, exp_len); + break; + + case MBEDTLS_ERR_CIPHER_INVALID_PADDING: + TEST_EQUAL(ret, 0); + TEST_EQUAL(invalid_padding, ~(size_t) 0); + break; + + default: + TEST_EQUAL(ret, exp_ret); + break; } } /* END_CASE */ From 46ebc3a758c061312855b7faec4bff785509e792 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 27 Jul 2025 21:20:47 +0200 Subject: [PATCH 48/91] Note that the decrypted length is sensitive when there was padding The decrypted length reveals the amount of padding that was eliminated, and thus reveals partial information about the last ciphertext block. Signed-off-by: Gilles Peskine --- include/mbedtls/cipher.h | 9 +++++++++ tests/suites/test_suite_cipher.function | 3 +++ 2 files changed, 12 insertions(+) diff --git a/include/mbedtls/cipher.h b/include/mbedtls/cipher.h index 3778f44551..616c5543c9 100644 --- a/include/mbedtls/cipher.h +++ b/include/mbedtls/cipher.h @@ -991,6 +991,11 @@ int mbedtls_cipher_update(mbedtls_cipher_context_t *ctx, * buffer of at least block_size Bytes. * \param olen The length of the data written to the \p output buffer. * This may not be \c NULL. + * Note that when decrypting in a mode with padding, + * the actual output length is sensitive and may be + * used to mount a padding oracle attack (see warning + * above), although less efficiently than through + * the invalid-padding condition. * * \return \c 0 on success. * \return #MBEDTLS_ERR_CIPHER_BAD_INPUT_DATA on @@ -1025,6 +1030,10 @@ int mbedtls_cipher_finish(mbedtls_cipher_context_t *ctx, * buffer of at least block_size Bytes. * \param[out] olen The length of the data written to the \p output buffer. * This may not be \c NULL. + * Note that when decrypting in a mode with padding, + * the actual output length is sensitive and may be + * used to mount a padding oracle attack (see warning + * on mbedtls_cipher_finish()). * \param[out] invalid_padding * If this function returns \c 0 on decryption, * \p *invalid_padding is \c 0 if the ciphertext was diff --git a/tests/suites/test_suite_cipher.function b/tests/suites/test_suite_cipher.function index 9e3e026834..ac0264e3d9 100644 --- a/tests/suites/test_suite_cipher.function +++ b/tests/suites/test_suite_cipher.function @@ -921,6 +921,7 @@ void decrypt_test_vec_cf(int cipher_id, int pad_mode, data_t *key, total_len += outlen; int actual_finish_result = mbedtls_cipher_finish(&ctx, output + outlen, &outlen); + TEST_CF_PUBLIC(&outlen, sizeof(outlen)); TEST_EQUAL(actual_finish_result, expected_finish_result); if (0 != expected_finish_result) { /* Check output parameter is set to the least-harmful value on error */ @@ -1083,6 +1084,7 @@ void decrypt_padded_test_vec_cf(int cipher_id, int pad_mode, data_t *key, int actual_finish_result = mbedtls_cipher_finish_padded(&ctx, output + outlen, &outlen, &invalid_padding); + TEST_CF_PUBLIC(&outlen, sizeof(outlen)); switch (expected_finish_result) { case 0: TEST_EQUAL(actual_finish_result, 0); @@ -1439,6 +1441,7 @@ void test_vec_crypt_cf(int cipher_id, int pad_mode, int operation, data_t *key, mbedtls_cipher_crypt(&ctx, iv->len ? iv->x : NULL, iv->len, input->x, input->len, output, &outlen); + TEST_CF_PUBLIC(&outlen, sizeof(outlen)); TEST_EQUAL(expected_finish_result, actual_finish_result); /* check plaintext only if everything went fine */ From 4eba1cc364c2a973f660e19d4398ac4f009cd7dc Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 8 Aug 2025 13:30:03 +0200 Subject: [PATCH 49/91] Improve outcome reporting of additional valgrind_cf testing Signed-off-by: Gilles Peskine --- tests/scripts/components-configuration-crypto.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/scripts/components-configuration-crypto.sh b/tests/scripts/components-configuration-crypto.sh index 6b12bb9af5..580de3dd75 100644 --- a/tests/scripts/components-configuration-crypto.sh +++ b/tests/scripts/components-configuration-crypto.sh @@ -23,6 +23,11 @@ test_with_valgrind () { ## Exclude suites that contain some constant-time tests, but whose focus ## isn't on constant-time tests. test_with_valgrind_constant_time () { + # Use a different configuration name in the outcome file if we're doing + # additional valgrind testing on top of non-instrumented testing. + if [[ $MBEDTLS_TEST_CONFIGURATION != *valgrind_cf* ]]; then + declare MBEDTLS_TEST_CONFIGURATION="${MBEDTLS_TEST_CONFIGURATION}+valgrind_cf" + fi declare GLOBIGNORE="tests/suites/test_suite_constant_time_hmac.data" test_with_valgrind tests/suites/*constant_time*.data } From beb53af31ff92e25c92c0d862cb94f430560f1f1 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 8 Aug 2025 13:35:46 +0200 Subject: [PATCH 50/91] Expand the ignore patterns for test_full_block_cipher_psa_dispatch Some tests from `test_suite_cipher.constant_time.data` follow the same pattern as `test_suite_cipher.aes.data` and so have the same coverage discrepancy. Signed-off-by: Gilles Peskine --- tests/scripts/analyze_outcomes.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/scripts/analyze_outcomes.py b/tests/scripts/analyze_outcomes.py index 52034a1973..9d03d1db46 100755 --- a/tests/scripts/analyze_outcomes.py +++ b/tests/scripts/analyze_outcomes.py @@ -667,6 +667,12 @@ class DriverVSReference_block_cipher_dispatch(outcome_analysis.DriverVSReference 'CMAC null arguments', re.compile('CMAC.* (AES|ARIA|Camellia).*'), ], + 'test_suite_cipher.constant_time': [ + # Like with test_suite_cipher.aes and such, these tests call + # cipher_wrapper in a way that requires the block cipher to + # be built in. + re.compile('.*(AES|ARIA|CAMELLIA).*(encrypt|decrypt).*', re.I), + ], 'test_suite_cipher.padding': [ # Following tests require AES_C/CAMELLIA_C to be enabled, # but these are not available in the accelerated component. From 7db50d070043c94c9c49a5860a23ab72afb97b65 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 7 Aug 2025 23:11:26 +0200 Subject: [PATCH 51/91] Changelog entry for mbedtls_cipher_finish_padded() Signed-off-by: Gilles Peskine --- ChangeLog.d/mbedtls_cipher_finish_padded.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 ChangeLog.d/mbedtls_cipher_finish_padded.txt diff --git a/ChangeLog.d/mbedtls_cipher_finish_padded.txt b/ChangeLog.d/mbedtls_cipher_finish_padded.txt new file mode 100644 index 0000000000..bf2405eb41 --- /dev/null +++ b/ChangeLog.d/mbedtls_cipher_finish_padded.txt @@ -0,0 +1,4 @@ +Features + * The new function mbedtls_cipher_finish_padded() is similar to + mbedtls_cipher_finish(), but makes it easier to process invalid-padding + conditions in constant time. From b4b1920f7a7c36f7ea71d431e159f9a08be3102a Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 8 Aug 2025 13:28:22 +0200 Subject: [PATCH 52/91] We now run some tests with MBEDTLS_TEST_CONSTANT_FLOW_VALGRIND Addresses https://github.com/Mbed-TLS/mbedtls/issues/9586 . This is not a fully satisfactory resolution, because we don't run every constant-flow test with Valgrind in PR jobs, only a small subset. We should improve the coverage/resource balance. Signed-off-by: Gilles Peskine --- tests/scripts/analyze_outcomes.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/scripts/analyze_outcomes.py b/tests/scripts/analyze_outcomes.py index 9d03d1db46..7b43145151 100755 --- a/tests/scripts/analyze_outcomes.py +++ b/tests/scripts/analyze_outcomes.py @@ -98,9 +98,6 @@ class CoverageTask(outcome_analysis.CoverageTask): 'Config: MBEDTLS_SHA256_USE_A64_CRYPTO_ONLY', 'Config: MBEDTLS_SHA256_USE_ARMV8_A_CRYPTO_ONLY', 'Config: MBEDTLS_SHA512_USE_A64_CRYPTO_ONLY', - # We don't run test_suite_config when we test this. - # https://github.com/Mbed-TLS/mbedtls/issues/9586 - 'Config: MBEDTLS_TEST_CONSTANT_FLOW_VALGRIND', ], 'test_suite_config.psa_boolean': [ # We don't test with HMAC disabled. From 00076b4907f67c3e6eb60ab89994fa5125319296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 7 Aug 2025 12:44:11 +0200 Subject: [PATCH 53/91] Expand testing for mbedtls_mpi_inv_mod() again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the next refactoring we'll have: - pretty different paths for N odd or even, - possibly different paths for A <= 0, in [0, N) or above, - possibly special cases when A % N is 0 or 1. Pick two small moduli of different parities (3 and 4) and go over the range [-(N+1), 2N-1] with A. This should ensure we naturally run into all special cases. Signed-off-by: Manuel Pégourié-Gonnard --- tests/suites/test_suite_bignum.misc.data | 69 ++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/suites/test_suite_bignum.misc.data b/tests/suites/test_suite_bignum.misc.data index 7b0c1e7323..4d8f2509e3 100644 --- a/tests/suites/test_suite_bignum.misc.data +++ b/tests/suites/test_suite_bignum.misc.data @@ -1525,6 +1525,75 @@ mpi_gcd:"-9986dabb54d13cd9fe0d9da594a97e8372ab26ed98ff622b31b1ea42e3a265019039ac Base test mbedtls_mpi_inv_mod #1 mpi_inv_mod:"3":"b":"4":0 +Base test mbedtls_mpi_inv_mod: 0 mod 3 +mpi_inv_mod:"0":"3":"":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE + +Base test mbedtls_mpi_inv_mod: 1 mod 3 +mpi_inv_mod:"1":"3":"1":0 + +Base test mbedtls_mpi_inv_mod: 2 mod 3 +mpi_inv_mod:"2":"3":"2":0 + +Base test mbedtls_mpi_inv_mod: 3 mod 3 +mpi_inv_mod:"0":"3":"":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE + +Base test mbedtls_mpi_inv_mod: 4 mod 3 +mpi_inv_mod:"4":"3":"1":0 + +Base test mbedtls_mpi_inv_mod: 5 mod 3 +mpi_inv_mod:"5":"3":"2":0 + +Base test mbedtls_mpi_inv_mod: -1 mod 3 +mpi_inv_mod:"-1":"3":"2":0 + +Base test mbedtls_mpi_inv_mod: -2 mod 3 +mpi_inv_mod:"-2":"3":"1":0 + +Base test mbedtls_mpi_inv_mod: -3 mod 3 +mpi_inv_mod:"-3":"3":"":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE + +Base test mbedtls_mpi_inv_mod: -4 mod 3 +mpi_inv_mod:"-4":"3":"2":0 + +Base test mbedtls_mpi_inv_mod: 0 mod 4 +mpi_inv_mod:"0":"4":"":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE + +Base test mbedtls_mpi_inv_mod: 1 mod 4 +mpi_inv_mod:"1":"4":"1":0 + +Base test mbedtls_mpi_inv_mod: 2 mod 4 +mpi_inv_mod:"2":"4":"":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE + +Base test mbedtls_mpi_inv_mod: 3 mod 4 +mpi_inv_mod:"3":"4":"3":0 + +Base test mbedtls_mpi_inv_mod: 4 mod 4 +mpi_inv_mod:"4":"4":"":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE + +Base test mbedtls_mpi_inv_mod: 5 mod 4 +mpi_inv_mod:"5":"4":"1":0 + +Base test mbedtls_mpi_inv_mod: 6 mod 4 +mpi_inv_mod:"6":"4":"":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE + +Base test mbedtls_mpi_inv_mod: 7 mod 4 +mpi_inv_mod:"7":"4":"3":0 + +Base test mbedtls_mpi_inv_mod: -1 mod 4 +mpi_inv_mod:"-1":"4":"3":0 + +Base test mbedtls_mpi_inv_mod: -2 mod 4 +mpi_inv_mod:"-2":"4":"":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE + +Base test mbedtls_mpi_inv_mod: -3 mod 4 +mpi_inv_mod:"-3":"4":"1":0 + +Base test mbedtls_mpi_inv_mod: -4 mod 4 +mpi_inv_mod:"-4":"4":"":MBEDTLS_ERR_MPI_NOT_ACCEPTABLE + +Base test mbedtls_mpi_inv_mod: -5 mod 4 +mpi_inv_mod:"-5":"4":"3":0 + Test mbedtls_mpi_inv_mod: mod 0 (null) mpi_inv_mod:"3":"":"0":MBEDTLS_ERR_MPI_BAD_INPUT_DATA From cdfd1c9c7d35309d43b0ccc122b96ee0d861b7b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 8 Aug 2025 09:21:23 +0200 Subject: [PATCH 54/91] bignum: use CT modinv when N is odd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/library/bignum.c b/library/bignum.c index 4f7ca16cda..617a7abf3c 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -1968,6 +1968,35 @@ int mbedtls_mpi_random(mbedtls_mpi *X, return mbedtls_mpi_core_random(X->p, min, N->p, X->n, f_rng, p_rng); } +/* + * Modular inverse: X = A^-1 mod N with N odd (and A any range) + */ +static int mbedtls_mpi_inv_mod_odd(mbedtls_mpi *X, + const mbedtls_mpi *A, + const mbedtls_mpi *N) +{ + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + mbedtls_mpi T, G; + + mbedtls_mpi_init(&T); + mbedtls_mpi_init(&G); + + MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(&T, A, N)); + MBEDTLS_MPI_CHK(mbedtls_mpi_gcd_modinv_odd(&G, &T, &T, N)); + if (mbedtls_mpi_cmp_int(&G, 1) != 0) { + ret = MBEDTLS_ERR_MPI_NOT_ACCEPTABLE; + goto cleanup; + } + + MBEDTLS_MPI_CHK(mbedtls_mpi_copy(X, &T)); + +cleanup: + mbedtls_mpi_free(&T); + mbedtls_mpi_free(&G); + + return ret; +} + /* * Modular inverse: X = A^-1 mod N (HAC 14.61 / 14.64) */ @@ -1980,6 +2009,10 @@ int mbedtls_mpi_inv_mod(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; } + if (mbedtls_mpi_get_bit(N, 0) == 1) { + return mbedtls_mpi_inv_mod_odd(X, A, N); + } + mbedtls_mpi_init(&TA); mbedtls_mpi_init(&TU); mbedtls_mpi_init(&U1); mbedtls_mpi_init(&U2); mbedtls_mpi_init(&G); mbedtls_mpi_init(&TB); mbedtls_mpi_init(&TV); mbedtls_mpi_init(&V1); mbedtls_mpi_init(&V2); From e41709c17eb4be2cacbf77262f6746aa8c499958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 8 Aug 2025 09:23:43 +0200 Subject: [PATCH 55/91] bignum: use CT modinv when A is odd and in [2, N) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/library/bignum.c b/library/bignum.c index 617a7abf3c..c742fc9cb5 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -1997,6 +1997,54 @@ cleanup: return ret; } +/* + * Compute X = A^-1 mod N with N even, A odd and 1 < A < N. + * + * This is not obvious because our constant-time modinv function only works with + * an odd modulus, and here the modulus is even. The idea is that computing a + * a^-1 mod b is really just computing the u coefficient in the Bézout relation + * a*u + b*v = 1 (assuming gcd(a,b) = 1, ie the inverse exists). But if we know + * one of u, v in this relation then the other is easy to find. So we can + * actually start by computing N^-1 mod A with gives us "the wrong half" of the + * Bézout relation, from which we'll deduce the interesting half A^-1 mod N. + * + * Return MBEDTLS_ERR_MPI_NOT_ACCEPTABLE if the inverse doesn't exist. + */ +static int mbedtls_mpi_inv_mod_even_in_range(mbedtls_mpi *X, + mbedtls_mpi const *A, + mbedtls_mpi const *N) +{ + int ret; + mbedtls_mpi I, G; + + mbedtls_mpi_init(&I); + mbedtls_mpi_init(&G); + + /* Set I = N^-1 mod A */ + MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(&I, N, A)); + MBEDTLS_MPI_CHK(mbedtls_mpi_gcd_modinv_odd(&G, &I, &I, A)); + if (mbedtls_mpi_cmp_int(&G, 1) != 0) { + ret = MBEDTLS_ERR_MPI_NOT_ACCEPTABLE; + goto cleanup; + } + + /* We know N * I = 1 + k * A for some k, which we can easily compute + * as k = (N*I - 1) / A (we know there will be no remainder). */ + MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(&I, &I, N)); + MBEDTLS_MPI_CHK(mbedtls_mpi_sub_int(&I, &I, 1)); + MBEDTLS_MPI_CHK(mbedtls_mpi_div_mpi(&G, NULL, &I, A)); + + /* Now we have a Bézout relation N * (previous value of I) - G * A = 1, + * so A^-1 mod N is -G mod N, which is N - G. + * Note that 0 < k < N since 0 < I < A, so G (k) is already in range. */ + MBEDTLS_MPI_CHK(mbedtls_mpi_sub_mpi(X, N, &G)); + +cleanup: + mbedtls_mpi_free(&I); + mbedtls_mpi_free(&G); + return ret; +} + /* * Modular inverse: X = A^-1 mod N (HAC 14.61 / 14.64) */ @@ -2013,6 +2061,12 @@ int mbedtls_mpi_inv_mod(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi return mbedtls_mpi_inv_mod_odd(X, A, N); } + if (mbedtls_mpi_get_bit(A, 0) == 1 && + mbedtls_mpi_cmp_int(A, 1) > 0 && + mbedtls_mpi_cmp_mpi(A, N) < 0) { + return mbedtls_mpi_inv_mod_even_in_range(X, A, N); + } + mbedtls_mpi_init(&TA); mbedtls_mpi_init(&TU); mbedtls_mpi_init(&U1); mbedtls_mpi_init(&U2); mbedtls_mpi_init(&G); mbedtls_mpi_init(&TB); mbedtls_mpi_init(&TV); mbedtls_mpi_init(&V1); mbedtls_mpi_init(&V2); From 1ac0a1e0714683a9dc2c87c2944d8e82d9bc87f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 8 Aug 2025 09:25:28 +0200 Subject: [PATCH 56/91] bignum: use CT modinv when A is odd (any range) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum.c | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/library/bignum.c b/library/bignum.c index c742fc9cb5..137afb07bc 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -2045,6 +2045,42 @@ cleanup: return ret; } +/* + * Compute X = A^-1 mod N with N even and A odd (but in any range). + * + * Return MBEDTLS_ERR_MPI_NOT_ACCEPTABLE if the inverse doesn't exist. + */ +static int mbedtls_mpi_inv_mod_even(mbedtls_mpi *X, + mbedtls_mpi const *A, + mbedtls_mpi const *N) +{ + int ret; + mbedtls_mpi AA; + + mbedtls_mpi_init(&AA); + + /* Bring A in the range [0, N). */ + MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(&AA, A, N)); + + /* We know A >= 0 but the next functions wants A > 1 */ + int cmp = mbedtls_mpi_cmp_int(&AA, 1); + if (cmp < 0) { // AA == 0 + ret = MBEDTLS_ERR_MPI_NOT_ACCEPTABLE; + goto cleanup; + } + if (cmp == 0) { // AA = 1 + MBEDTLS_MPI_CHK(mbedtls_mpi_lset(X, 1)); + goto cleanup; + } + + /* Now we know 1 < A < N, N is even and AA is still odd */ + MBEDTLS_MPI_CHK(mbedtls_mpi_inv_mod_even_in_range(X, &AA, N)); + +cleanup: + mbedtls_mpi_free(&AA); + return ret; +} + /* * Modular inverse: X = A^-1 mod N (HAC 14.61 / 14.64) */ @@ -2061,10 +2097,8 @@ int mbedtls_mpi_inv_mod(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi return mbedtls_mpi_inv_mod_odd(X, A, N); } - if (mbedtls_mpi_get_bit(A, 0) == 1 && - mbedtls_mpi_cmp_int(A, 1) > 0 && - mbedtls_mpi_cmp_mpi(A, N) < 0) { - return mbedtls_mpi_inv_mod_even_in_range(X, A, N); + if (mbedtls_mpi_get_bit(A, 0) == 1) { + return mbedtls_mpi_inv_mod_even(X, A, N); } mbedtls_mpi_init(&TA); mbedtls_mpi_init(&TU); mbedtls_mpi_init(&U1); mbedtls_mpi_init(&U2); From 40dfc811efe4c10b77333b4edcfc6805bb49beac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 8 Aug 2025 09:27:29 +0200 Subject: [PATCH 57/91] bignum: remove dead variable-time inv_mod code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum.c | 83 +++--------------------------------------------- 1 file changed, 5 insertions(+), 78 deletions(-) diff --git a/library/bignum.c b/library/bignum.c index 137afb07bc..bd09710ba4 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -2082,13 +2082,12 @@ cleanup: } /* - * Modular inverse: X = A^-1 mod N (HAC 14.61 / 14.64) + * Modular inverse: X = A^-1 mod N + * + * Wrapper around mbedtls_mpi_gcd_modinv_odd() that lifts its limitations. */ int mbedtls_mpi_inv_mod(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *N) { - int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; - mbedtls_mpi G, TA, TU, U1, U2, TB, TV, V1, V2; - if (mbedtls_mpi_cmp_int(N, 1) <= 0) { return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; } @@ -2101,80 +2100,8 @@ int mbedtls_mpi_inv_mod(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi return mbedtls_mpi_inv_mod_even(X, A, N); } - mbedtls_mpi_init(&TA); mbedtls_mpi_init(&TU); mbedtls_mpi_init(&U1); mbedtls_mpi_init(&U2); - mbedtls_mpi_init(&G); mbedtls_mpi_init(&TB); mbedtls_mpi_init(&TV); - mbedtls_mpi_init(&V1); mbedtls_mpi_init(&V2); - - MBEDTLS_MPI_CHK(mbedtls_mpi_gcd(&G, A, N)); - - if (mbedtls_mpi_cmp_int(&G, 1) != 0) { - ret = MBEDTLS_ERR_MPI_NOT_ACCEPTABLE; - goto cleanup; - } - - MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(&TA, A, N)); - MBEDTLS_MPI_CHK(mbedtls_mpi_copy(&TU, &TA)); - MBEDTLS_MPI_CHK(mbedtls_mpi_copy(&TB, N)); - MBEDTLS_MPI_CHK(mbedtls_mpi_copy(&TV, N)); - - MBEDTLS_MPI_CHK(mbedtls_mpi_lset(&U1, 1)); - MBEDTLS_MPI_CHK(mbedtls_mpi_lset(&U2, 0)); - MBEDTLS_MPI_CHK(mbedtls_mpi_lset(&V1, 0)); - MBEDTLS_MPI_CHK(mbedtls_mpi_lset(&V2, 1)); - - do { - while ((TU.p[0] & 1) == 0) { - MBEDTLS_MPI_CHK(mbedtls_mpi_shift_r(&TU, 1)); - - if ((U1.p[0] & 1) != 0 || (U2.p[0] & 1) != 0) { - MBEDTLS_MPI_CHK(mbedtls_mpi_add_mpi(&U1, &U1, &TB)); - MBEDTLS_MPI_CHK(mbedtls_mpi_sub_mpi(&U2, &U2, &TA)); - } - - MBEDTLS_MPI_CHK(mbedtls_mpi_shift_r(&U1, 1)); - MBEDTLS_MPI_CHK(mbedtls_mpi_shift_r(&U2, 1)); - } - - while ((TV.p[0] & 1) == 0) { - MBEDTLS_MPI_CHK(mbedtls_mpi_shift_r(&TV, 1)); - - if ((V1.p[0] & 1) != 0 || (V2.p[0] & 1) != 0) { - MBEDTLS_MPI_CHK(mbedtls_mpi_add_mpi(&V1, &V1, &TB)); - MBEDTLS_MPI_CHK(mbedtls_mpi_sub_mpi(&V2, &V2, &TA)); - } - - MBEDTLS_MPI_CHK(mbedtls_mpi_shift_r(&V1, 1)); - MBEDTLS_MPI_CHK(mbedtls_mpi_shift_r(&V2, 1)); - } - - if (mbedtls_mpi_cmp_mpi(&TU, &TV) >= 0) { - MBEDTLS_MPI_CHK(mbedtls_mpi_sub_mpi(&TU, &TU, &TV)); - MBEDTLS_MPI_CHK(mbedtls_mpi_sub_mpi(&U1, &U1, &V1)); - MBEDTLS_MPI_CHK(mbedtls_mpi_sub_mpi(&U2, &U2, &V2)); - } else { - MBEDTLS_MPI_CHK(mbedtls_mpi_sub_mpi(&TV, &TV, &TU)); - MBEDTLS_MPI_CHK(mbedtls_mpi_sub_mpi(&V1, &V1, &U1)); - MBEDTLS_MPI_CHK(mbedtls_mpi_sub_mpi(&V2, &V2, &U2)); - } - } while (mbedtls_mpi_cmp_int(&TU, 0) != 0); - - while (mbedtls_mpi_cmp_int(&V1, 0) < 0) { - MBEDTLS_MPI_CHK(mbedtls_mpi_add_mpi(&V1, &V1, N)); - } - - while (mbedtls_mpi_cmp_mpi(&V1, N) >= 0) { - MBEDTLS_MPI_CHK(mbedtls_mpi_sub_mpi(&V1, &V1, N)); - } - - MBEDTLS_MPI_CHK(mbedtls_mpi_copy(X, &V1)); - -cleanup: - - mbedtls_mpi_free(&TA); mbedtls_mpi_free(&TU); mbedtls_mpi_free(&U1); mbedtls_mpi_free(&U2); - mbedtls_mpi_free(&G); mbedtls_mpi_free(&TB); mbedtls_mpi_free(&TV); - mbedtls_mpi_free(&V1); mbedtls_mpi_free(&V2); - - return ret; + /* If A and N are both even, 2 divides they GCD, so no inverse. */ + return MBEDTLS_ERR_MPI_NOT_ACCEPTABLE; } #if defined(MBEDTLS_GENPRIME) From 65b8011f7ece8e320c28369b0e04713d35400836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 10 Jul 2025 21:26:42 +0200 Subject: [PATCH 58/91] bignum: make mbedtls_mpi_lsb() less leaky MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The path using builtin should be OK, as it should be using dedicated CPU instructions which are constant time. This fixes the no-builing path. GCC gained support for __has_builtin in version 10. We're still testing with older GCC on the CI, so the non-builtin path is tested on the CI. https://gcc.gnu.org/gcc-10/changes.html Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum.c | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/library/bignum.c b/library/bignum.c index 4f7ca16cda..432ecb9e37 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -430,13 +430,6 @@ cleanup: return ret; } -/* - * Return the number of less significant zero-bits - */ -size_t mbedtls_mpi_lsb(const mbedtls_mpi *X) -{ - size_t i; - #if defined(__has_builtin) #if (MBEDTLS_MPI_UINT_MAX == UINT_MAX) && __has_builtin(__builtin_ctz) #define mbedtls_mpi_uint_ctz __builtin_ctz @@ -447,22 +440,34 @@ size_t mbedtls_mpi_lsb(const mbedtls_mpi *X) #endif #endif -#if defined(mbedtls_mpi_uint_ctz) +#if !defined(mbedtls_mpi_uint_ctz) +static size_t mbedtls_mpi_uint_ctz(mbedtls_mpi_uint x) +{ + size_t count = 0; + mbedtls_ct_condition_t done = MBEDTLS_CT_FALSE; + + for (size_t i = 0; i < biL; i++) { + mbedtls_ct_condition_t non_zero = mbedtls_ct_bool((x >> i) & 1); + done = mbedtls_ct_bool_or(done, non_zero); + count = mbedtls_ct_size_if(done, count, i + 1); + } + + return count; +} +#endif + +/* + * Return the number of less significant zero-bits + */ +size_t mbedtls_mpi_lsb(const mbedtls_mpi *X) +{ + size_t i; + for (i = 0; i < X->n; i++) { if (X->p[i] != 0) { return i * biL + mbedtls_mpi_uint_ctz(X->p[i]); } } -#else - size_t count = 0; - for (i = 0; i < X->n; i++) { - for (size_t j = 0; j < biL; j++, count++) { - if (((X->p[i] >> j) & 1) != 0) { - return count; - } - } - } -#endif return 0; } From 7a5447ff65bb5d0092ca73bfc953c0e7445f1795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 12 Aug 2025 09:18:28 +0200 Subject: [PATCH 59/91] Fix a few typos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Felix Conway Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/bignum.c b/library/bignum.c index bd09710ba4..16b1449dd3 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -2003,7 +2003,7 @@ cleanup: * This is not obvious because our constant-time modinv function only works with * an odd modulus, and here the modulus is even. The idea is that computing a * a^-1 mod b is really just computing the u coefficient in the Bézout relation - * a*u + b*v = 1 (assuming gcd(a,b) = 1, ie the inverse exists). But if we know + * a*u + b*v = 1 (assuming gcd(a,b) = 1, i.e. the inverse exists). But if we know * one of u, v in this relation then the other is easy to find. So we can * actually start by computing N^-1 mod A with gives us "the wrong half" of the * Bézout relation, from which we'll deduce the interesting half A^-1 mod N. @@ -2062,7 +2062,7 @@ static int mbedtls_mpi_inv_mod_even(mbedtls_mpi *X, /* Bring A in the range [0, N). */ MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(&AA, A, N)); - /* We know A >= 0 but the next functions wants A > 1 */ + /* We know A >= 0 but the next function wants A > 1 */ int cmp = mbedtls_mpi_cmp_int(&AA, 1); if (cmp < 0) { // AA == 0 ret = MBEDTLS_ERR_MPI_NOT_ACCEPTABLE; @@ -2100,7 +2100,7 @@ int mbedtls_mpi_inv_mod(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi return mbedtls_mpi_inv_mod_even(X, A, N); } - /* If A and N are both even, 2 divides they GCD, so no inverse. */ + /* If A and N are both even, 2 divides their GCD, so no inverse. */ return MBEDTLS_ERR_MPI_NOT_ACCEPTABLE; } From a08faf90700e46f6aa2a6e3fee8a6a71ddb816ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 12 Aug 2025 09:24:15 +0200 Subject: [PATCH 60/91] bignum: follow customs for ret initialisation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/bignum.c b/library/bignum.c index 16b1449dd3..ccccff51b6 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -2014,7 +2014,7 @@ static int mbedtls_mpi_inv_mod_even_in_range(mbedtls_mpi *X, mbedtls_mpi const *A, mbedtls_mpi const *N) { - int ret; + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; mbedtls_mpi I, G; mbedtls_mpi_init(&I); @@ -2054,7 +2054,7 @@ static int mbedtls_mpi_inv_mod_even(mbedtls_mpi *X, mbedtls_mpi const *A, mbedtls_mpi const *N) { - int ret; + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; mbedtls_mpi AA; mbedtls_mpi_init(&AA); From c6a9d845555e229d224d15b3f8a8370390e9ef6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 10 Jul 2025 23:28:50 +0200 Subject: [PATCH 61/91] bignum: use CT gcd for mbedtls_mpi_gcd() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The overall function is still not constant-time, but it just got a lot less leaky. Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum.c | 108 ++++++++++++----------------------------------- 1 file changed, 26 insertions(+), 82 deletions(-) diff --git a/library/bignum.c b/library/bignum.c index 432ecb9e37..7d5103ebb8 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -1819,103 +1819,47 @@ cleanup: } /* - * Greatest common divisor: G = gcd(A, B) (HAC 14.54) + * Greatest common divisor: G = gcd(A, B) + * Wrapper around mbedtls_mpi_gcd_modinv() that removes its restrictions. */ int mbedtls_mpi_gcd(mbedtls_mpi *G, const mbedtls_mpi *A, const mbedtls_mpi *B) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; - size_t lz, lzt; mbedtls_mpi TA, TB; mbedtls_mpi_init(&TA); mbedtls_mpi_init(&TB); + /* Make copies and take absolute values */ MBEDTLS_MPI_CHK(mbedtls_mpi_copy(&TA, A)); MBEDTLS_MPI_CHK(mbedtls_mpi_copy(&TB, B)); - - lz = mbedtls_mpi_lsb(&TA); - lzt = mbedtls_mpi_lsb(&TB); - - /* The loop below gives the correct result when A==0 but not when B==0. - * So have a special case for B==0. Leverage the fact that we just - * calculated the lsb and lsb(B)==0 iff B is odd or 0 to make the test - * slightly more efficient than cmp_int(). */ - if (lzt == 0 && mbedtls_mpi_get_bit(&TB, 0) == 0) { - ret = mbedtls_mpi_copy(G, A); - goto cleanup; - } - - if (lzt < lz) { - lz = lzt; - } - TA.s = TB.s = 1; - /* We mostly follow the procedure described in HAC 14.54, but with some - * minor differences: - * - Sequences of multiplications or divisions by 2 are grouped into a - * single shift operation. - * - The procedure in HAC assumes that 0 < TB <= TA. - * - The condition TB <= TA is not actually necessary for correctness. - * TA and TB have symmetric roles except for the loop termination - * condition, and the shifts at the beginning of the loop body - * remove any significance from the ordering of TA vs TB before - * the shifts. - * - If TA = 0, the loop goes through 0 iterations and the result is - * correctly TB. - * - The case TB = 0 was short-circuited above. - * - * For the correctness proof below, decompose the original values of - * A and B as - * A = sa * 2^a * A' with A'=0 or A' odd, and sa = +-1 - * B = sb * 2^b * B' with B'=0 or B' odd, and sb = +-1 - * Then gcd(A, B) = 2^{min(a,b)} * gcd(A',B'), - * and gcd(A',B') is odd or 0. - * - * At the beginning, we have TA = |A| and TB = |B| so gcd(A,B) = gcd(TA,TB). - * The code maintains the following invariant: - * gcd(A,B) = 2^k * gcd(TA,TB) for some k (I) - */ - - /* Proof that the loop terminates: - * At each iteration, either the right-shift by 1 is made on a nonzero - * value and the nonnegative integer bitlen(TA) + bitlen(TB) decreases - * by at least 1, or the right-shift by 1 is made on zero and then - * TA becomes 0 which ends the loop (TB cannot be 0 if it is right-shifted - * since in that case TB is calculated from TB-TA with the condition TB>TA). - */ - while (mbedtls_mpi_cmp_int(&TA, 0) != 0) { - /* Divisions by 2 preserve the invariant (I). */ - MBEDTLS_MPI_CHK(mbedtls_mpi_shift_r(&TA, mbedtls_mpi_lsb(&TA))); - MBEDTLS_MPI_CHK(mbedtls_mpi_shift_r(&TB, mbedtls_mpi_lsb(&TB))); - - /* Set either TA or TB to |TA-TB|/2. Since TA and TB are both odd, - * TA-TB is even so the division by 2 has an integer result. - * Invariant (I) is preserved since any odd divisor of both TA and TB - * also divides |TA-TB|/2, and any odd divisor of both TA and |TA-TB|/2 - * also divides TB, and any odd divisor of both TB and |TA-TB|/2 also - * divides TA. - */ - if (mbedtls_mpi_cmp_mpi(&TA, &TB) >= 0) { - MBEDTLS_MPI_CHK(mbedtls_mpi_sub_abs(&TA, &TA, &TB)); - MBEDTLS_MPI_CHK(mbedtls_mpi_shift_r(&TA, 1)); - } else { - MBEDTLS_MPI_CHK(mbedtls_mpi_sub_abs(&TB, &TB, &TA)); - MBEDTLS_MPI_CHK(mbedtls_mpi_shift_r(&TB, 1)); - } - /* Note that one of TA or TB is still odd. */ + /* Handle special cases (that don't happen in crypto usage) */ + if (mbedtls_mpi_core_check_zero_ct(A.p, A.n) == MBEDTLS_CT_FALSE) { + return mbedtls_mpi_copy(G, TB); // GCD(0, B) = abs(B) + } + if (mbedtls_mpi_core_check_zero_ct(B.p, B.n) == MBEDTLS_CT_FALSE) { + return mbedtls_mpi_copy(G, A); // GCD(A, 0) = A (for now) } - /* By invariant (I), gcd(A,B) = 2^k * gcd(TA,TB) for some k. - * At the loop exit, TA = 0, so gcd(TA,TB) = TB. - * - If there was at least one loop iteration, then one of TA or TB is odd, - * and TA = 0, so TB is odd and gcd(TA,TB) = gcd(A',B'). In this case, - * lz = min(a,b) so gcd(A,B) = 2^lz * TB. - * - If there was no loop iteration, then A was 0, and gcd(A,B) = B. - * In this case, lz = 0 and B = TB so gcd(A,B) = B = 2^lz * TB as well. - */ + /* Make the two values the same (non-zero) number of limbs */ + MBEDTLS_MPI_CHK(mbedtls_mpi_grow(&TA, TB.n != 0 ? TB.n : 1)); + MBEDTLS_MPI_CHK(mbedtls_mpi_grow(&TB, TA.n)); // non-zero from above - MBEDTLS_MPI_CHK(mbedtls_mpi_shift_l(&TB, lz)); - MBEDTLS_MPI_CHK(mbedtls_mpi_copy(G, &TB)); + const size_t za = mbedtls_mpi_lsb(&TA); + const size_t zb = mbedtls_mpi_lsb(&TB); + + MBEDTLS_MPI_CHK(mbedtls_mpi_shift_r(&TA, za)); + MBEDTLS_MPI_CHK(mbedtls_mpi_shift_r(&TB, zb)); + + /* Ensure A <= B: if B < A, swap them */ + mbedtls_ct_condition_t swap = mbedtls_mpi_core_lt_ct(TB.p, TA.p, TA.n); + mbedtls_mpi_core_cond_swap(TA.p, TB.p, TA.n, swap); + + MBEDTLS_MPI_CHK(mbedtls_mpi_gcd_modinv_odd(G, NULL, &TA, &TB)); + + size_t zg = za > zb ? zb : za; // zg = min(za, zb) + MBEDTLS_MPI_CHK(mbedtls_mpi_shift_l(G, zg)); cleanup: From 381d4ba03be79df1a3d2777aa74cdf11f5d0b5b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Mon, 4 Aug 2025 10:57:13 +0200 Subject: [PATCH 62/91] Make mbedtls_mpi_gcd() more consistent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- ChangeLog.d/gcd-sign.txt | 5 +++++ include/mbedtls/bignum.h | 3 +-- library/bignum.c | 19 ++++++++++--------- tests/suites/test_suite_bignum.misc.data | 4 ++-- 4 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 ChangeLog.d/gcd-sign.txt diff --git a/ChangeLog.d/gcd-sign.txt b/ChangeLog.d/gcd-sign.txt new file mode 100644 index 0000000000..52d1e1f24f --- /dev/null +++ b/ChangeLog.d/gcd-sign.txt @@ -0,0 +1,5 @@ +Changes + * The function mbedtls_mpi_gcd() now always gives a non-negative output. + Previously the output was negative when B = 0 and A < 0, which was not + documented, and inconsistent as all other inputs resulted in a non-negative + output. diff --git a/include/mbedtls/bignum.h b/include/mbedtls/bignum.h index 297c0a6ba4..6187856713 100644 --- a/include/mbedtls/bignum.h +++ b/include/mbedtls/bignum.h @@ -974,8 +974,7 @@ int mbedtls_mpi_random(mbedtls_mpi *X, * \brief Compute the greatest common divisor: G = gcd(A, B) * * \param G The destination MPI. This must point to an initialized MPI. - * This will be positive unless \p B is 0, in which case \p A - * will be returned, where \p A could be negative. + * This will always be positive or 0. * \param A The first operand. This must point to an initialized MPI. * \param B The second operand. This must point to an initialized MPI. * diff --git a/library/bignum.c b/library/bignum.c index 7d5103ebb8..96cade4bfe 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -1834,18 +1834,19 @@ int mbedtls_mpi_gcd(mbedtls_mpi *G, const mbedtls_mpi *A, const mbedtls_mpi *B) MBEDTLS_MPI_CHK(mbedtls_mpi_copy(&TB, B)); TA.s = TB.s = 1; - /* Handle special cases (that don't happen in crypto usage) */ - if (mbedtls_mpi_core_check_zero_ct(A.p, A.n) == MBEDTLS_CT_FALSE) { - return mbedtls_mpi_copy(G, TB); // GCD(0, B) = abs(B) - } - if (mbedtls_mpi_core_check_zero_ct(B.p, B.n) == MBEDTLS_CT_FALSE) { - return mbedtls_mpi_copy(G, A); // GCD(A, 0) = A (for now) - } - - /* Make the two values the same (non-zero) number of limbs */ + /* Make the two values the same (non-zero) number of limbs. + * This is needed to use mbedtls_mpi_core functions below. */ MBEDTLS_MPI_CHK(mbedtls_mpi_grow(&TA, TB.n != 0 ? TB.n : 1)); MBEDTLS_MPI_CHK(mbedtls_mpi_grow(&TB, TA.n)); // non-zero from above + /* Handle special cases (that don't happen in crypto usage) */ + if (mbedtls_mpi_core_check_zero_ct(TA.p, TA.n) == MBEDTLS_CT_FALSE) { + return mbedtls_mpi_copy(G, &TB); // GCD(0, B) = abs(B) + } + if (mbedtls_mpi_core_check_zero_ct(TB.p, TB.n) == MBEDTLS_CT_FALSE) { + return mbedtls_mpi_copy(G, &TA); // GCD(A, 0) = abs(A) + } + const size_t za = mbedtls_mpi_lsb(&TA); const size_t zb = mbedtls_mpi_lsb(&TB); diff --git a/tests/suites/test_suite_bignum.misc.data b/tests/suites/test_suite_bignum.misc.data index 7b0c1e7323..4798512e76 100644 --- a/tests/suites/test_suite_bignum.misc.data +++ b/tests/suites/test_suite_bignum.misc.data @@ -1466,10 +1466,10 @@ Test GCD: 6, 0 (1 limb) mpi_gcd:"06":"00":"6" Test GCD: negative, 0 (null) -mpi_gcd:"-50000":"":"-50000" +mpi_gcd:"-50000":"":"50000" Test GCD: negative, 0 (1 limb) -mpi_gcd:"-a782374b2ee927df28802745833a":"00":"-a782374b2ee927df28802745833a" +mpi_gcd:"-a782374b2ee927df28802745833a":"00":"a782374b2ee927df28802745833a" Test GCD: 0 (null), negative mpi_gcd:"":"-50000":"50000" From 87e77d6516c6161e6cfe3043fefa22e62d32c885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Mon, 11 Aug 2025 10:45:41 +0200 Subject: [PATCH 63/91] bignum: fix memory leak in GCD with 0 as an input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/bignum.c b/library/bignum.c index 96cade4bfe..358714839c 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -1841,10 +1841,12 @@ int mbedtls_mpi_gcd(mbedtls_mpi *G, const mbedtls_mpi *A, const mbedtls_mpi *B) /* Handle special cases (that don't happen in crypto usage) */ if (mbedtls_mpi_core_check_zero_ct(TA.p, TA.n) == MBEDTLS_CT_FALSE) { - return mbedtls_mpi_copy(G, &TB); // GCD(0, B) = abs(B) + MBEDTLS_MPI_CHK(mbedtls_mpi_copy(G, &TB)); // GCD(0, B) = abs(B) + goto cleanup; } if (mbedtls_mpi_core_check_zero_ct(TB.p, TB.n) == MBEDTLS_CT_FALSE) { - return mbedtls_mpi_copy(G, &TA); // GCD(A, 0) = abs(A) + MBEDTLS_MPI_CHK(mbedtls_mpi_copy(G, &TA)); // GCD(A, 0) = abs(A) + goto cleanup; } const size_t za = mbedtls_mpi_lsb(&TA); From 30f073236922fe4e528fdf82eb442babb50516fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Wed, 13 Aug 2025 08:42:45 +0200 Subject: [PATCH 64/91] bignum: gcd: improve comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/bignum.c b/library/bignum.c index 358714839c..d03e26c0a7 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -1849,9 +1849,9 @@ int mbedtls_mpi_gcd(mbedtls_mpi *G, const mbedtls_mpi *A, const mbedtls_mpi *B) goto cleanup; } + /* Make boths inputs odd by putting powers of 2 on the side */ const size_t za = mbedtls_mpi_lsb(&TA); const size_t zb = mbedtls_mpi_lsb(&TB); - MBEDTLS_MPI_CHK(mbedtls_mpi_shift_r(&TA, za)); MBEDTLS_MPI_CHK(mbedtls_mpi_shift_r(&TB, zb)); @@ -1861,6 +1861,7 @@ int mbedtls_mpi_gcd(mbedtls_mpi *G, const mbedtls_mpi *A, const mbedtls_mpi *B) MBEDTLS_MPI_CHK(mbedtls_mpi_gcd_modinv_odd(G, NULL, &TA, &TB)); + /* Re-inject the power of 2 we had previously put aside */ size_t zg = za > zb ? zb : za; // zg = min(za, zb) MBEDTLS_MPI_CHK(mbedtls_mpi_shift_l(G, zg)); From a56a05b0152f4f669d7f1ece9271ed23be34c944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 10 Jul 2025 21:40:15 +0200 Subject: [PATCH 65/91] RSA: use CT gcd-modinv in prepare_blinding() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While at it, draw the blinding value uniformly in the permissible range. Signed-off-by: Manuel Pégourié-Gonnard --- library/rsa.c | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/library/rsa.c b/library/rsa.c index 557faaf363..b7df690fb5 100644 --- a/library/rsa.c +++ b/library/rsa.c @@ -1304,33 +1304,16 @@ static int rsa_prepare_blinding(mbedtls_rsa_context *ctx, } /* Unblinding value: Vf = random number, invertible mod N */ + mbedtls_mpi_lset(&R, 0); do { if (count++ > 10) { ret = MBEDTLS_ERR_RSA_RNG_FAILED; goto cleanup; } - MBEDTLS_MPI_CHK(mbedtls_mpi_fill_random(&ctx->Vf, ctx->len - 1, f_rng, p_rng)); - - /* Compute Vf^-1 as R * (R Vf)^-1 to avoid leaks from inv_mod. */ - MBEDTLS_MPI_CHK(mbedtls_mpi_fill_random(&R, ctx->len - 1, f_rng, p_rng)); - MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(&ctx->Vi, &ctx->Vf, &R)); - MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(&ctx->Vi, &ctx->Vi, &ctx->N)); - - /* At this point, Vi is invertible mod N if and only if both Vf and R - * are invertible mod N. If one of them isn't, we don't need to know - * which one, we just loop and choose new values for both of them. - * (Each iteration succeeds with overwhelming probability.) */ - ret = mbedtls_mpi_inv_mod(&ctx->Vi, &ctx->Vi, &ctx->N); - if (ret != 0 && ret != MBEDTLS_ERR_MPI_NOT_ACCEPTABLE) { - goto cleanup; - } - - } while (ret == MBEDTLS_ERR_MPI_NOT_ACCEPTABLE); - - /* Finish the computation of Vf^-1 = R * (R Vf)^-1 */ - MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(&ctx->Vi, &ctx->Vi, &R)); - MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(&ctx->Vi, &ctx->Vi, &ctx->N)); + MBEDTLS_MPI_CHK(mbedtls_mpi_random(&ctx->Vf, 1, &ctx->N, f_rng, p_rng)); + MBEDTLS_MPI_CHK(mbedtls_mpi_gcd_modinv_odd(&R, &ctx->Vi, &ctx->Vf, &ctx->N)); + } while (mbedtls_mpi_cmp_int(&R, 1) != 0); /* Blinding value: Vi = Vf^(-e) mod N * (Vi already contains Vf^-1 at this point) */ From c2d210ea0db99bb30bfc574537b223e8573df53f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 10 Jul 2025 21:48:41 +0200 Subject: [PATCH 66/91] DHM: use CT modinv for blinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/dhm.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/library/dhm.c b/library/dhm.c index bcc07f5441..3c65bc8076 100644 --- a/library/dhm.c +++ b/library/dhm.c @@ -18,6 +18,7 @@ #if defined(MBEDTLS_DHM_C) #include "mbedtls/dhm.h" +#include "bignum_internal.h" #include "mbedtls/platform_util.h" #include "mbedtls/error.h" @@ -381,16 +382,8 @@ static int dhm_update_blinding(mbedtls_dhm_context *ctx, /* Vi = random( 2, P-2 ) */ MBEDTLS_MPI_CHK(dhm_random_below(&ctx->Vi, &ctx->P, f_rng, p_rng)); - /* Vf = Vi^-X mod P - * First compute Vi^-1 = R * (R Vi)^-1, (avoiding leaks from inv_mod), - * then elevate to the Xth power. */ - MBEDTLS_MPI_CHK(dhm_random_below(&R, &ctx->P, f_rng, p_rng)); - MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(&ctx->Vf, &ctx->Vi, &R)); - MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(&ctx->Vf, &ctx->Vf, &ctx->P)); - MBEDTLS_MPI_CHK(mbedtls_mpi_inv_mod(&ctx->Vf, &ctx->Vf, &ctx->P)); - MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(&ctx->Vf, &ctx->Vf, &R)); - MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(&ctx->Vf, &ctx->Vf, &ctx->P)); - + /* Vf = Vi^-X mod P */ + MBEDTLS_MPI_CHK(mbedtls_mpi_gcd_modinv_odd(NULL, &ctx->Vf, &ctx->Vi, &ctx->P)); MBEDTLS_MPI_CHK(mbedtls_mpi_exp_mod(&ctx->Vf, &ctx->Vf, &ctx->X, &ctx->P, &ctx->RP)); cleanup: From f35d30799cc9ec07d46265d31384139a6bb6c09f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 10 Jul 2025 21:54:38 +0200 Subject: [PATCH 67/91] ECP: use CT modinv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A function that was previously called in multiple places is now called only once, hence more susceptible to being inlined, hence the test fix. Signed-off-by: Manuel Pégourié-Gonnard --- library/ecp.c | 30 ++----------------- .../components-configuration-crypto.sh | 3 +- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/library/ecp.c b/library/ecp.c index fdd00a59c5..6af516c0ac 100644 --- a/library/ecp.c +++ b/library/ecp.c @@ -68,6 +68,7 @@ #include "mbedtls/error.h" #include "bn_mul.h" +#include "bignum_internal.h" #include "ecp_invasive.h" #include @@ -1173,7 +1174,7 @@ cleanup: MBEDTLS_MPI_CHK(mbedtls_mpi_mul_int_mod(grp, X, A, c)) #define MPI_ECP_INV(dst, src) \ - MBEDTLS_MPI_CHK(mbedtls_mpi_inv_mod((dst), (src), &grp->P)) + MBEDTLS_MPI_CHK(mbedtls_mpi_gcd_modinv_odd(NULL, (dst), (src), &grp->P)) #define MPI_ECP_MOV(X, A) \ MBEDTLS_MPI_CHK(mbedtls_mpi_copy(X, A)) @@ -2201,21 +2202,6 @@ static int ecp_mul_comb_after_precomp(const mbedtls_ecp_group *grp, final_norm: MBEDTLS_ECP_BUDGET(MBEDTLS_ECP_OPS_INV); #endif - /* - * Knowledge of the jacobian coordinates may leak the last few bits of the - * scalar [1], and since our MPI implementation isn't constant-flow, - * inversion (used for coordinate normalization) may leak the full value - * of its input via side-channels [2]. - * - * [1] https://eprint.iacr.org/2003/191 - * [2] https://eprint.iacr.org/2020/055 - * - * Avoid the leak by randomizing coordinates before we normalize them. - */ - if (f_rng != 0) { - MBEDTLS_MPI_CHK(ecp_randomize_jac(grp, RR, f_rng, p_rng)); - } - MBEDTLS_MPI_CHK(ecp_normalize_jac(grp, RR)); #if defined(MBEDTLS_ECP_RESTARTABLE) @@ -2594,18 +2580,6 @@ static int ecp_mul_mxz(mbedtls_ecp_group *grp, mbedtls_ecp_point *R, MPI_ECP_COND_SWAP(&R->Z, &RP.Z, b); } - /* - * Knowledge of the projective coordinates may leak the last few bits of the - * scalar [1], and since our MPI implementation isn't constant-flow, - * inversion (used for coordinate normalization) may leak the full value - * of its input via side-channels [2]. - * - * [1] https://eprint.iacr.org/2003/191 - * [2] https://eprint.iacr.org/2020/055 - * - * Avoid the leak by randomizing coordinates before we normalize them. - */ - MBEDTLS_MPI_CHK(ecp_randomize_mxz(grp, R, f_rng, p_rng)); MBEDTLS_MPI_CHK(ecp_normalize_mxz(grp, R)); cleanup: diff --git a/tests/scripts/components-configuration-crypto.sh b/tests/scripts/components-configuration-crypto.sh index 04c38f6c36..11a37059c3 100644 --- a/tests/scripts/components-configuration-crypto.sh +++ b/tests/scripts/components-configuration-crypto.sh @@ -1293,7 +1293,8 @@ common_test_psa_crypto_config_accel_ecc_some_curves () { ALG_SHA3_224 ALG_SHA3_256 ALG_SHA3_384 ALG_SHA3_512" helper_libtestdriver1_make_drivers "$loc_accel_list" "$loc_extra_list" - helper_libtestdriver1_make_main "$loc_accel_list" + # For grep to work below we need less inlining in ecp.c + ASAN_CFLAGS="$ASAN_CFLAGS -O0" helper_libtestdriver1_make_main "$loc_accel_list" # We expect ECDH to be re-enabled for the missing curves grep mbedtls_ecdh_ library/ecdh.o From 0d73de5ee0ec65ff8b60d073b7d1d369ee0a00e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 10 Jul 2025 22:59:39 +0200 Subject: [PATCH 68/91] ecdsa: use CT modinv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/ecdsa.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/library/ecdsa.c b/library/ecdsa.c index 2f7a996a7e..0d73c65425 100644 --- a/library/ecdsa.c +++ b/library/ecdsa.c @@ -17,6 +17,7 @@ #include "mbedtls/ecdsa.h" #include "mbedtls/asn1write.h" +#include "bignum_internal.h" #include @@ -340,21 +341,11 @@ modn: MBEDTLS_MPI_CHK(derive_mpi(grp, &e, buf, blen)); /* - * Generate a random value to blind inv_mod in next step, - * avoiding a potential timing leak. - */ - MBEDTLS_MPI_CHK(mbedtls_ecp_gen_privkey(grp, &t, f_rng_blind, - p_rng_blind)); - - /* - * Step 6: compute s = (e + r * d) / k = t (e + rd) / (kt) mod n + * Step 6: compute s = (e + r * d) / k */ MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(s, pr, d)); MBEDTLS_MPI_CHK(mbedtls_mpi_add_mpi(&e, &e, s)); - MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(&e, &e, &t)); - MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(pk, pk, &t)); - MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(pk, pk, &grp->N)); - MBEDTLS_MPI_CHK(mbedtls_mpi_inv_mod(s, pk, &grp->N)); + MBEDTLS_MPI_CHK(mbedtls_mpi_gcd_modinv_odd(NULL, s, pk, &grp->N)); MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(s, s, &e)); MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(s, s, &grp->N)); } while (mbedtls_mpi_cmp_int(s, 0) == 0); @@ -540,7 +531,7 @@ int mbedtls_ecdsa_verify_restartable(mbedtls_ecp_group *grp, */ ECDSA_BUDGET(MBEDTLS_ECP_OPS_CHK + MBEDTLS_ECP_OPS_INV + 2); - MBEDTLS_MPI_CHK(mbedtls_mpi_inv_mod(&s_inv, s, &grp->N)); + MBEDTLS_MPI_CHK(mbedtls_mpi_gcd_modinv_odd(NULL, &s_inv, s, &grp->N)); MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(pu1, &e, &s_inv)); MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(pu1, pu1, &grp->N)); From 7dcfd7373170ef718863c0c61cda4ff3e5e25299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 10 Jul 2025 09:57:29 +0200 Subject: [PATCH 69/91] RSA: use constant-time GCD in deduce_primes() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/rsa_alt_helpers.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/rsa_alt_helpers.c b/library/rsa_alt_helpers.c index 5c265a9921..feb7874b8b 100644 --- a/library/rsa_alt_helpers.c +++ b/library/rsa_alt_helpers.c @@ -12,6 +12,7 @@ #include "mbedtls/rsa.h" #include "mbedtls/bignum.h" +#include "bignum_internal.h" #include "rsa_alt_helpers.h" /* @@ -117,7 +118,7 @@ int mbedtls_rsa_deduce_primes(mbedtls_mpi const *N, MBEDTLS_MPI_CHK(mbedtls_mpi_lset(&K, primes[attempt])); /* Check if gcd(K,N) = 1 */ - MBEDTLS_MPI_CHK(mbedtls_mpi_gcd(P, &K, N)); + MBEDTLS_MPI_CHK(mbedtls_mpi_gcd_modinv_odd(P, NULL, &K, N)); if (mbedtls_mpi_cmp_int(P, 1) != 0) { continue; } @@ -136,7 +137,7 @@ int mbedtls_rsa_deduce_primes(mbedtls_mpi const *N, } MBEDTLS_MPI_CHK(mbedtls_mpi_add_int(&K, &K, 1)); - MBEDTLS_MPI_CHK(mbedtls_mpi_gcd(P, &K, N)); + MBEDTLS_MPI_CHK(mbedtls_mpi_gcd_modinv_odd(P, NULL, &K, N)); if (mbedtls_mpi_cmp_int(P, 1) == 1 && mbedtls_mpi_cmp_mpi(P, N) == -1) { From 630148e67f0cd3d337308321f72f444ca4ef1c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Wed, 13 Aug 2025 13:57:35 +0200 Subject: [PATCH 70/91] RSA: use constant-time modinv in deduce_crt() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum.c | 6 +++--- library/bignum_internal.h | 18 ++++++++++++++++++ library/rsa_alt_helpers.c | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/library/bignum.c b/library/bignum.c index f7ec35a9df..00aa79ca2f 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -1924,9 +1924,9 @@ int mbedtls_mpi_random(mbedtls_mpi *X, /* * Modular inverse: X = A^-1 mod N with N odd (and A any range) */ -static int mbedtls_mpi_inv_mod_odd(mbedtls_mpi *X, - const mbedtls_mpi *A, - const mbedtls_mpi *N) +int mbedtls_mpi_inv_mod_odd(mbedtls_mpi *X, + const mbedtls_mpi *A, + const mbedtls_mpi *N) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; mbedtls_mpi T, G; diff --git a/library/bignum_internal.h b/library/bignum_internal.h index f3f6fcbc8d..a947497007 100644 --- a/library/bignum_internal.h +++ b/library/bignum_internal.h @@ -80,4 +80,22 @@ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, const mbedtls_mpi *A, const mbedtls_mpi *N); +/** + * \brief Modular inverse: X = A^-1 mod N with N odd + * + * \param[out] X The inverse of \p A modulo \p N on success, + * indeterminate otherwise. + * \param[in] A The number to invert. + * \param[in] N The modulus. Must be odd and greater than 1. + * + * \return \c 0 if successful. + * \return #MBEDTLS_ERR_MPI_ALLOC_FAILED if a memory allocation failed. + * \return #MBEDTLS_ERR_MPI_BAD_INPUT_DATA if preconditions were not + * met. + * \return #MBEDTLS_ERR_MPI_NOT_ACCEPTABLE if A is not invertible mod N. + */ +int mbedtls_mpi_inv_mod_odd(mbedtls_mpi *X, + const mbedtls_mpi *A, + const mbedtls_mpi *N); + #endif /* bignum_internal.h */ diff --git a/library/rsa_alt_helpers.c b/library/rsa_alt_helpers.c index feb7874b8b..d91949af12 100644 --- a/library/rsa_alt_helpers.c +++ b/library/rsa_alt_helpers.c @@ -245,7 +245,7 @@ int mbedtls_rsa_deduce_crt(const mbedtls_mpi *P, const mbedtls_mpi *Q, /* QP = Q^{-1} mod P */ if (QP != NULL) { - MBEDTLS_MPI_CHK(mbedtls_mpi_inv_mod(QP, Q, P)); + MBEDTLS_MPI_CHK(mbedtls_mpi_inv_mod_odd(QP, Q, P)); } cleanup: From a4bf680e92dc5be758a730daaad7bf6eac5d7353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 10 Jul 2025 10:48:23 +0200 Subject: [PATCH 71/91] RSA: refactor: avoid code duplication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/rsa.c | 35 ++++++++++++----------------------- library/rsa_alt_helpers.c | 5 ++++- library/rsa_alt_helpers.h | 13 ++++++++----- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/library/rsa.c b/library/rsa.c index b7df690fb5..08267dbfce 100644 --- a/library/rsa.c +++ b/library/rsa.c @@ -1047,7 +1047,7 @@ int mbedtls_rsa_gen_key(mbedtls_rsa_context *ctx, unsigned int nbits, int exponent) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; - mbedtls_mpi H, G, L; + mbedtls_mpi H; int prime_quality = 0; /* @@ -1060,8 +1060,6 @@ int mbedtls_rsa_gen_key(mbedtls_rsa_context *ctx, } mbedtls_mpi_init(&H); - mbedtls_mpi_init(&G); - mbedtls_mpi_init(&L); if (exponent < 3 || nbits % 2 != 0) { ret = MBEDTLS_ERR_RSA_BAD_INPUT_DATA; @@ -1099,35 +1097,28 @@ int mbedtls_rsa_gen_key(mbedtls_rsa_context *ctx, mbedtls_mpi_swap(&ctx->P, &ctx->Q); } - /* Temporarily replace P,Q by P-1, Q-1 */ - MBEDTLS_MPI_CHK(mbedtls_mpi_sub_int(&ctx->P, &ctx->P, 1)); - MBEDTLS_MPI_CHK(mbedtls_mpi_sub_int(&ctx->Q, &ctx->Q, 1)); - MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(&H, &ctx->P, &ctx->Q)); - - /* check GCD( E, (P-1)*(Q-1) ) == 1 (FIPS 186-4 §B.3.1 criterion 2(a)) */ - MBEDTLS_MPI_CHK(mbedtls_mpi_gcd(&G, &ctx->E, &H)); - if (mbedtls_mpi_cmp_int(&G, 1) != 0) { + /* Compute D = E^-1 mod LCM(P-1, Q-1) (FIPS 186-4 §B.3.1 criterion 3(b)) + * if it exists (FIPS 186-4 §B.3.1 criterion 2(a)) */ + ret = mbedtls_rsa_deduce_private_exponent(&ctx->P, &ctx->Q, &ctx->E, &ctx->D); + if (ret == MBEDTLS_ERR_MPI_NOT_ACCEPTABLE) { + mbedtls_mpi_lset(&ctx->D, 0); /* needed for the next call */ continue; } + if (ret != 0) { + goto cleanup; + } - /* compute smallest possible D = E^-1 mod LCM(P-1, Q-1) (FIPS 186-4 §B.3.1 criterion 3(b)) */ - MBEDTLS_MPI_CHK(mbedtls_mpi_gcd(&G, &ctx->P, &ctx->Q)); - MBEDTLS_MPI_CHK(mbedtls_mpi_div_mpi(&L, NULL, &H, &G)); - MBEDTLS_MPI_CHK(mbedtls_mpi_inv_mod(&ctx->D, &ctx->E, &L)); - - if (mbedtls_mpi_bitlen(&ctx->D) <= ((nbits + 1) / 2)) { // (FIPS 186-4 §B.3.1 criterion 3(a)) + /* (FIPS 186-4 §B.3.1 criterion 3(a)) */ + if (mbedtls_mpi_bitlen(&ctx->D) <= ((nbits + 1) / 2)) { continue; } break; } while (1); - /* Restore P,Q */ - MBEDTLS_MPI_CHK(mbedtls_mpi_add_int(&ctx->P, &ctx->P, 1)); - MBEDTLS_MPI_CHK(mbedtls_mpi_add_int(&ctx->Q, &ctx->Q, 1)); + /* N = P * Q */ MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(&ctx->N, &ctx->P, &ctx->Q)); - ctx->len = mbedtls_mpi_size(&ctx->N); #if !defined(MBEDTLS_RSA_NO_CRT) @@ -1146,8 +1137,6 @@ int mbedtls_rsa_gen_key(mbedtls_rsa_context *ctx, cleanup: mbedtls_mpi_free(&H); - mbedtls_mpi_free(&G); - mbedtls_mpi_free(&L); if (ret != 0) { mbedtls_rsa_free(ctx); diff --git a/library/rsa_alt_helpers.c b/library/rsa_alt_helpers.c index d91949af12..08adbe3eb8 100644 --- a/library/rsa_alt_helpers.c +++ b/library/rsa_alt_helpers.c @@ -212,7 +212,10 @@ int mbedtls_rsa_deduce_private_exponent(mbedtls_mpi const *P, MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(&K, &K, &L)); MBEDTLS_MPI_CHK(mbedtls_mpi_div_mpi(&K, NULL, &K, D)); - /* Compute modular inverse of E in LCM(P-1, Q-1) */ + /* Compute modular inverse of E mod LCM(P-1, Q-1) + * This is FIPS 186-4 §B.3.1 criterion 3(b). + * This will return MBEDTLS_ERR_MPI_NOT_ACCEPTABLE if E is not coprime to + * (P-1)(Q-1), also validating FIPS 186-4 §B.3.1 criterion 2(a). */ MBEDTLS_MPI_CHK(mbedtls_mpi_inv_mod(D, E, &K)); cleanup: diff --git a/library/rsa_alt_helpers.h b/library/rsa_alt_helpers.h index 052b02491e..580ffb2fba 100644 --- a/library/rsa_alt_helpers.h +++ b/library/rsa_alt_helpers.h @@ -89,12 +89,15 @@ int mbedtls_rsa_deduce_primes(mbedtls_mpi const *N, mbedtls_mpi const *E, * \param P First prime factor of RSA modulus * \param Q Second prime factor of RSA modulus * \param E RSA public exponent - * \param D Pointer to MPI holding the private exponent on success. + * \param D Pointer to MPI holding the private exponent on success, + * ie the modular inverse of E modulo LCM(P-1,Q-1). * - * \return - * - 0 if successful. In this case, D is set to a simultaneous - * modular inverse of E modulo both P-1 and Q-1. - * - A non-zero error code otherwise. + * \return \c 0 if successful. + * \return #MBEDTLS_ERR_MPI_ALLOC_FAILED if a memory allocation failed. + * \return #MBEDTLS_ERR_MPI_NOT_ACCEPTABLE if E is not coprime to P-1 + * and Q-1, that is, if GCD( E, (P-1)*(Q-1) ) != 1. + * \return #MBEDTLS_ERR_MPI_BAD_INPUT_DATA if inputs are otherwise + * invalid. * * \note This function does not check whether P and Q are primes. * From 9e1c532847af26ea6504720c6755b668b4b1d0da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Wed, 13 Aug 2025 14:14:19 +0200 Subject: [PATCH 72/91] RSA: use CT gcd-modinv in deduce_private_exponent() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum.c | 6 +++--- library/bignum_internal.h | 20 ++++++++++++++++++++ library/rsa_alt_helpers.c | 6 +++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/library/bignum.c b/library/bignum.c index 00aa79ca2f..f6b8f99981 100644 --- a/library/bignum.c +++ b/library/bignum.c @@ -1963,9 +1963,9 @@ cleanup: * * Return MBEDTLS_ERR_MPI_NOT_ACCEPTABLE if the inverse doesn't exist. */ -static int mbedtls_mpi_inv_mod_even_in_range(mbedtls_mpi *X, - mbedtls_mpi const *A, - mbedtls_mpi const *N) +int mbedtls_mpi_inv_mod_even_in_range(mbedtls_mpi *X, + mbedtls_mpi const *A, + mbedtls_mpi const *N) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; mbedtls_mpi I, G; diff --git a/library/bignum_internal.h b/library/bignum_internal.h index a947497007..341346222d 100644 --- a/library/bignum_internal.h +++ b/library/bignum_internal.h @@ -98,4 +98,24 @@ int mbedtls_mpi_inv_mod_odd(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *N); +/** + * \brief Modular inverse: X = A^-1 mod N with N even, + * A odd and 1 < A < N. + * + * \param[out] X The inverse of \p A modulo \p N on success, + * indeterminate otherwise. + * \param[in] A The number to invert. Must be odd, greated than 1 + * and less than \p N. + * \param[in] N The modulus. Must be even and greater than 1. + * + * \return \c 0 if successful. + * \return #MBEDTLS_ERR_MPI_ALLOC_FAILED if a memory allocation failed. + * \return #MBEDTLS_ERR_MPI_BAD_INPUT_DATA if preconditions were not + * met. + * \return #MBEDTLS_ERR_MPI_NOT_ACCEPTABLE if A is not invertible mod N. + */ +int mbedtls_mpi_inv_mod_even_in_range(mbedtls_mpi *X, + mbedtls_mpi const *A, + mbedtls_mpi const *N); + #endif /* bignum_internal.h */ diff --git a/library/rsa_alt_helpers.c b/library/rsa_alt_helpers.c index 08adbe3eb8..50a5c4e0d7 100644 --- a/library/rsa_alt_helpers.c +++ b/library/rsa_alt_helpers.c @@ -198,6 +198,10 @@ int mbedtls_rsa_deduce_private_exponent(mbedtls_mpi const *P, return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; } + if (mbedtls_mpi_get_bit(E, 0) != 1) { + return MBEDTLS_ERR_MPI_NOT_ACCEPTABLE; + } + mbedtls_mpi_init(&K); mbedtls_mpi_init(&L); @@ -216,7 +220,7 @@ int mbedtls_rsa_deduce_private_exponent(mbedtls_mpi const *P, * This is FIPS 186-4 §B.3.1 criterion 3(b). * This will return MBEDTLS_ERR_MPI_NOT_ACCEPTABLE if E is not coprime to * (P-1)(Q-1), also validating FIPS 186-4 §B.3.1 criterion 2(a). */ - MBEDTLS_MPI_CHK(mbedtls_mpi_inv_mod(D, E, &K)); + MBEDTLS_MPI_CHK(mbedtls_mpi_inv_mod_even_in_range(D, E, &K)); cleanup: From f845e9d1113c9459b3b313e1b0d71ddc66b130fb Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 25 Aug 2025 16:48:42 +0200 Subject: [PATCH 73/91] Minor documentation improvements Signed-off-by: Gilles Peskine --- include/mbedtls/cipher.h | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/include/mbedtls/cipher.h b/include/mbedtls/cipher.h index 616c5543c9..e547152155 100644 --- a/include/mbedtls/cipher.h +++ b/include/mbedtls/cipher.h @@ -885,23 +885,24 @@ int mbedtls_cipher_set_iv(mbedtls_cipher_context_t *ctx, * * \note With non-AEAD ciphers, the order of calls for each message * is as follows: - * 1. mbedtls_cipher_set_iv() if the mode uses an IV/nonce. - * 2. mbedtls_cipher_reset() - * 3. mbedtls_cipher_update() one or more times - * 4. mbedtls_cipher_finish() or mbedtls_cipher_finish_padded() + * 1. mbedtls_cipher_set_iv() if the mode uses an IV/nonce; + * 2. mbedtls_cipher_reset(); + * 3. mbedtls_cipher_update() zero, one or more times; + * 4. mbedtls_cipher_finish_padded() (recommended for decryption + * if the mode uses padding) or mbedtls_cipher_finish(). * . * This sequence can be repeated to encrypt or decrypt multiple * messages with the same key. * * \note With AEAD ciphers, the order of calls for each message * is as follows: - * 1. mbedtls_cipher_set_iv() if the mode uses an IV/nonce. - * 2. mbedtls_cipher_reset() - * 3. mbedtls_cipher_update_ad() - * 4. mbedtls_cipher_update() one or more times - * 5. mbedtls_cipher_finish() or mbedtls_cipher_finish_padded() + * 1. mbedtls_cipher_set_iv() if the mode uses an IV/nonce; + * 2. mbedtls_cipher_reset(); + * 3. mbedtls_cipher_update_ad(); + * 4. mbedtls_cipher_update() zero, one or more times; + * 5. mbedtls_cipher_finish() (or mbedtls_cipher_finish_padded()); * 6. mbedtls_cipher_check_tag() (for decryption) or - * mbedtls_cipher_write_tag() (for encryption). + * mbedtls_cipher_write_tag() (for encryption). * . * This sequence can be repeated to encrypt or decrypt multiple * messages with the same key. @@ -982,7 +983,7 @@ int mbedtls_cipher_update(mbedtls_cipher_context_t *ctx, * Therefore applications that call this function for * decryption with a cipher that involves padding * should take care around error handling. Preferably, - * such applicatios should use + * such applications should use * mbedtls_cipher_finish_padded() instead of this function. * * \param ctx The generic cipher context. This must be initialized and @@ -1061,7 +1062,8 @@ int mbedtls_cipher_finish_padded(mbedtls_cipher_context_t *ctx, /** * \brief This function writes a tag for AEAD ciphers. * Currently supported with GCM and ChaCha20+Poly1305. - * This must be called after mbedtls_cipher_finish(). + * This must be called after mbedtls_cipher_finish() + * or mbedtls_cipher_finish_padded(). * * \param ctx The generic cipher context. This must be initialized, * bound to a key, and have just completed a cipher @@ -1080,7 +1082,8 @@ int mbedtls_cipher_write_tag(mbedtls_cipher_context_t *ctx, /** * \brief This function checks the tag for AEAD ciphers. * Currently supported with GCM and ChaCha20+Poly1305. - * This must be called after mbedtls_cipher_finish(). + * This must be called after mbedtls_cipher_finish() + * or mbedtls_cipher_finish_padded(). * * \param ctx The generic cipher context. This must be initialized. * \param tag The buffer holding the tag. This must be a readable From 94e4e15748d7018c4a8aa6c63f554b21ab754b58 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 25 Aug 2025 16:53:54 +0200 Subject: [PATCH 74/91] Explain the near-duplication of test function for constant-flow tests Signed-off-by: Gilles Peskine --- tests/suites/test_suite_cipher.function | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/suites/test_suite_cipher.function b/tests/suites/test_suite_cipher.function index ac0264e3d9..96c336086b 100644 --- a/tests/suites/test_suite_cipher.function +++ b/tests/suites/test_suite_cipher.function @@ -873,6 +873,13 @@ exit: /* END_CASE */ /* BEGIN_CASE */ +/* Similar to decrypt_test_vec, but with constant-flow assertions. + * We use a separate test function so that we can run the functional tests + * in all configurations where the underlying cipher is enabled, and + * run the constant-flow tests only in configurations where the underlying + * cipher is constant-time. In particular, AES test cases need to depend + * on HAVE_CONSTANT_TIME_AES. + */ void decrypt_test_vec_cf(int cipher_id, int pad_mode, data_t *key, data_t *iv, data_t *cipher, data_t *clear, data_t *ad, data_t *tag, @@ -1033,6 +1040,13 @@ exit: /* END_CASE */ /* BEGIN_CASE */ +/* Similar to decrypt_padded_test_vec, but with constant-flow assertions. + * We use a separate test function so that we can run the functional tests + * in all configurations where the underlying cipher is enabled, and + * run the constant-flow tests only in configurations where the underlying + * cipher is constant-time. In particular, AES test cases need to depend + * on HAVE_CONSTANT_TIME_AES. + */ void decrypt_padded_test_vec_cf(int cipher_id, int pad_mode, data_t *key, data_t *iv, data_t *cipher, data_t *clear, data_t *ad, data_t *tag, @@ -1404,6 +1418,13 @@ exit: /* END_CASE */ /* BEGIN_CASE depends_on:MBEDTLS_CIPHER_MODE_WITH_PADDING */ +/* Similar to test_vec_crypt, but with constant-flow assertions. + * We use a separate test function so that we can run the functional tests + * in all configurations where the underlying cipher is enabled, and + * run the constant-flow tests only in configurations where the underlying + * cipher is constant-time. In particular, AES test cases need to depend + * on HAVE_CONSTANT_TIME_AES. + */ void test_vec_crypt_cf(int cipher_id, int pad_mode, int operation, data_t *key, data_t *iv, data_t *input, data_t *result, int expected_finish_result, int use_psa) From 6ab0f519b8c9ede36b034e93fc191b14e8843891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 26 Aug 2025 11:31:52 +0200 Subject: [PATCH 75/91] dhm: remove unused variable (and improve comment) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/dhm.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/library/dhm.c b/library/dhm.c index 3c65bc8076..941a89da80 100644 --- a/library/dhm.c +++ b/library/dhm.c @@ -345,9 +345,6 @@ static int dhm_update_blinding(mbedtls_dhm_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng) { int ret; - mbedtls_mpi R; - - mbedtls_mpi_init(&R); /* * Don't use any blinding the first time a particular X is used, @@ -382,13 +379,11 @@ static int dhm_update_blinding(mbedtls_dhm_context *ctx, /* Vi = random( 2, P-2 ) */ MBEDTLS_MPI_CHK(dhm_random_below(&ctx->Vi, &ctx->P, f_rng, p_rng)); - /* Vf = Vi^-X mod P */ + /* Vf = Vi^-X = (Vi^-1)^X mod P */ MBEDTLS_MPI_CHK(mbedtls_mpi_gcd_modinv_odd(NULL, &ctx->Vf, &ctx->Vi, &ctx->P)); MBEDTLS_MPI_CHK(mbedtls_mpi_exp_mod(&ctx->Vf, &ctx->Vf, &ctx->X, &ctx->P, &ctx->RP)); cleanup: - mbedtls_mpi_free(&R); - return ret; } From b46432930eb8ecc3ed8953df56c77952c99e8f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 26 Aug 2025 11:33:12 +0200 Subject: [PATCH 76/91] ecdsa: rm unused variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/ecdsa.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/ecdsa.c b/library/ecdsa.c index 0d73c65425..a2d1eea6de 100644 --- a/library/ecdsa.c +++ b/library/ecdsa.c @@ -252,7 +252,7 @@ int mbedtls_ecdsa_sign_restartable(mbedtls_ecp_group *grp, int ret, key_tries, sign_tries; int *p_sign_tries = &sign_tries, *p_key_tries = &key_tries; mbedtls_ecp_point R; - mbedtls_mpi k, e, t; + mbedtls_mpi k, e; mbedtls_mpi *pk = &k, *pr = r; /* Fail cleanly on curves such as Curve25519 that can't be used for ECDSA */ @@ -266,7 +266,7 @@ int mbedtls_ecdsa_sign_restartable(mbedtls_ecp_group *grp, } mbedtls_ecp_point_init(&R); - mbedtls_mpi_init(&k); mbedtls_mpi_init(&e); mbedtls_mpi_init(&t); + mbedtls_mpi_init(&k); mbedtls_mpi_init(&e); ECDSA_RS_ENTER(sig); @@ -358,7 +358,7 @@ modn: cleanup: mbedtls_ecp_point_free(&R); - mbedtls_mpi_free(&k); mbedtls_mpi_free(&e); mbedtls_mpi_free(&t); + mbedtls_mpi_free(&k); mbedtls_mpi_free(&e); ECDSA_RS_LEAVE(sig); From c18eea6d43bf5d24e3f83945245e9fe31139228e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 26 Aug 2025 11:34:45 +0200 Subject: [PATCH 77/91] Minor grammar fix in comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/rsa_alt_helpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/rsa_alt_helpers.h b/library/rsa_alt_helpers.h index 580ffb2fba..8ff74a02bf 100644 --- a/library/rsa_alt_helpers.h +++ b/library/rsa_alt_helpers.h @@ -90,7 +90,7 @@ int mbedtls_rsa_deduce_primes(mbedtls_mpi const *N, mbedtls_mpi const *E, * \param Q Second prime factor of RSA modulus * \param E RSA public exponent * \param D Pointer to MPI holding the private exponent on success, - * ie the modular inverse of E modulo LCM(P-1,Q-1). + * i.e. the modular inverse of E modulo LCM(P-1,Q-1). * * \return \c 0 if successful. * \return #MBEDTLS_ERR_MPI_ALLOC_FAILED if a memory allocation failed. From 44765c4b9b104ad390d3525626aa4e72320c423b Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 26 Aug 2025 13:11:27 +0200 Subject: [PATCH 78/91] Test invalid_padding against all-bits-one `SIZE_MAX` and `~(size_t) 0` are the same, but since the documentation says "all-bits-one", write it that way in the test code. Signed-off-by: Gilles Peskine --- tests/suites/test_suite_cipher.function | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/suites/test_suite_cipher.function b/tests/suites/test_suite_cipher.function index 96c336086b..1a4d4799ea 100644 --- a/tests/suites/test_suite_cipher.function +++ b/tests/suites/test_suite_cipher.function @@ -1010,7 +1010,7 @@ void decrypt_padded_test_vec(int cipher_id, int pad_mode, data_t *key, break; case MBEDTLS_ERR_CIPHER_INVALID_PADDING: TEST_EQUAL(actual_finish_result, 0); - TEST_EQUAL(invalid_padding, SIZE_MAX); + TEST_EQUAL(invalid_padding, ~(size_t) 0); break; default: TEST_EQUAL(actual_finish_result, expected_finish_result); @@ -1106,7 +1106,7 @@ void decrypt_padded_test_vec_cf(int cipher_id, int pad_mode, data_t *key, break; case MBEDTLS_ERR_CIPHER_INVALID_PADDING: TEST_EQUAL(actual_finish_result, 0); - TEST_EQUAL(invalid_padding, SIZE_MAX); + TEST_EQUAL(invalid_padding, ~(size_t) 0); break; default: TEST_EQUAL(actual_finish_result, expected_finish_result); @@ -1523,7 +1523,7 @@ void check_padding(int pad_mode, data_t *input, break; case MBEDTLS_ERR_CIPHER_INVALID_PADDING: TEST_EQUAL(ret, 0); - TEST_EQUAL(invalid_padding, SIZE_MAX); + TEST_EQUAL(invalid_padding, ~(size_t) 0); break; default: TEST_EQUAL(ret, expected_ret); From 07cbb33e761b5c1e995e41ac8327d0aaa59332ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 2 Sep 2025 10:41:50 +0200 Subject: [PATCH 79/91] Add ChangeLog entry for SSBleed and M-Step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- ChangeLog.d/ssbleed-mstep.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 ChangeLog.d/ssbleed-mstep.txt diff --git a/ChangeLog.d/ssbleed-mstep.txt b/ChangeLog.d/ssbleed-mstep.txt new file mode 100644 index 0000000000..8fc9ef753f --- /dev/null +++ b/ChangeLog.d/ssbleed-mstep.txt @@ -0,0 +1,12 @@ +Security + * Fix a local timing side-channel in modular inversion and GCD that was + exploitable in RSA key generation and other RSA operations (see the full + advisory for details), allowing a local attacker to fully recover the + private key. This can be exploited on some Arm-v9 CPUs by an unprivileged + attacker running code on the same core (SSBleed), or when Trustzone-M is + used, by the non-secure side abusing timer interrupts (M-Step), and + probably in other similar settings as well. Found and reported + independently by: SSBleed: Chang Liu (Tsinghua University) and Trevor E. + Carlson (National University of Singapore); M-Step: Cristiano Rodrigues + (University of Minho), Marton Bognar (DistriNet, KU Leuven), Sandro Pinto + (University of Minho), Jo Van Bulck (DistriNet, KU Leuven). CVE-2025-54764 From bba5d7c439a35a4aa4244a1b69750e1c14dc9bed Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 27 Jul 2025 18:10:01 +0200 Subject: [PATCH 80/91] Add constant-time AES-CBC encrypt and decrypt tests through PSA The main goal is to validate that unpadding is constant-time, including error reporting. Use a separate test function, not annotations in the existing function, so that the functional tests can run on any platform, and we know from test outcomes where we have run the constant-time tests. The tests can only be actually constant-time if AES is constant time, since AES computations are part of what is checked. Thus this requires hardware-accelerated AES. We can't run our AESNI (or AESCE?) code under Msan (it doesn't detect when memory is written from assembly code), so these tests can only be run with Valgrind. Signed-off-by: Gilles Peskine --- .../test_suite_psa_crypto_constant_time.data | 39 +++++ ...st_suite_psa_crypto_constant_time.function | 161 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 tests/suites/test_suite_psa_crypto_constant_time.data create mode 100644 tests/suites/test_suite_psa_crypto_constant_time.function diff --git a/tests/suites/test_suite_psa_crypto_constant_time.data b/tests/suites/test_suite_psa_crypto_constant_time.data new file mode 100644 index 0000000000..9b4c64e6c9 --- /dev/null +++ b/tests/suites/test_suite_psa_crypto_constant_time.data @@ -0,0 +1,39 @@ +CT encrypt CHACHA20 +depends_on:PSA_WANT_ALG_STREAM_CIPHER:PSA_WANT_KEY_TYPE_CHACHA20 +ct_cipher_encrypt:PSA_ALG_STREAM_CIPHER:PSA_KEY_TYPE_CHACHA20:"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f":"000000000000004a00000000":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000":"af051e40bba0354981329a806a140eafd258a22a6dcb4bb9f6569cb3efe2deaf837bd87ca20b5ba12081a306af0eb35c41a239d20dfc74c81771560d9c9c1e4b224f51f3401bd9e12fde276fb8631ded8c131f823d2c06e27e4fcaec9ef3cf788a3b0aa372600a92b57974cded2b9334794cba40c63e34cdea212c4cf07d41b769a6749f3f630f4122cafe28ec4dc47e26d4346d70b98c73f3e9c53ac40c5945398b6eda1a832c89c167eacd901d7e2bf363" + +CT encrypt AES-CTR +depends_on:PSA_WANT_ALG_CTR:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_encrypt:PSA_ALG_CTR:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"dd3b5e5319b7591daab1e1a92687feb2":"396ee84fb75fdbb5c2b13c7fe5a654aa" + +CT encrypt AES-CBC-nopad +depends_on:PSA_WANT_ALG_CBC_NO_PADDING:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_encrypt:PSA_ALG_CBC_NO_PADDING:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"49e4e66c89a86b67758df89db9ad6955":"396ee84fb75fdbb5c2b13c7fe5a654aa" + +CT encrypt AES-CBC-PKCS7 +depends_on:PSA_WANT_ALG_CBC_PKCS7:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_encrypt:PSA_ALG_CBC_PKCS7:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"6bc1bee22e409f96e93d7e117393172a":"a076ec9dfbe47d52afc357336f20743bca7e8a15dc3c776436314293031cd4f3" + +CT decrypt CHACHA20 +depends_on:PSA_WANT_ALG_STREAM_CIPHER:PSA_WANT_KEY_TYPE_CHACHA20 +ct_cipher_decrypt:PSA_ALG_STREAM_CIPHER:PSA_KEY_TYPE_CHACHA20:"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f":"000000000000004a00000000":"af051e40bba0354981329a806a140eafd258a22a6dcb4bb9f6569cb3efe2deaf837bd87ca20b5ba12081a306af0eb35c41a239d20dfc74c81771560d9c9c1e4b224f51f3401bd9e12fde276fb8631ded8c131f823d2c06e27e4fcaec9ef3cf788a3b0aa372600a92b57974cded2b9334794cba40c63e34cdea212c4cf07d41b769a6749f3f630f4122cafe28ec4dc47e26d4346d70b98c73f3e9c53ac40c5945398b6eda1a832c89c167eacd901d7e2bf363":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000":0 + +CT decrypt AES-CTR +depends_on:PSA_WANT_ALG_CTR:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_decrypt:PSA_ALG_CTR:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"396ee84fb75fdbb5c2b13c7fe5a654aa":"dd3b5e5319b7591daab1e1a92687feb2":0 + +CT decrypt AES-CBC-nopad +depends_on:PSA_WANT_ALG_CBC_NO_PADDING:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_decrypt:PSA_ALG_CBC_NO_PADDING:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"396ee84fb75fdbb5c2b13c7fe5a654aa":"49e4e66c89a86b67758df89db9ad6955":0 + +CT decrypt AES-CBC-PKCS7 good +depends_on:PSA_WANT_ALG_CBC_PKCS7:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_decrypt:PSA_ALG_CBC_PKCS7:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"a076ec9dfbe47d52afc357336f20743bca7e8a15dc3c776436314293031cd4f3":"6bc1bee22e409f96e93d7e117393172a":0 + +CT decrypt AES-CBC-PKCS7 invalid padding @0 +depends_on:PSA_WANT_ALG_CBC_PKCS7:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_decrypt:PSA_ALG_CBC_PKCS7:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"a076ec9dfbe47d52afc357336f20743bf42ddf64c420325affb343d5d5f5d5dc":"6bc1bee22e409f96e93d7e117393172a":1 + +CT decrypt AES-CBC-PKCS7 invalid padding @16 +depends_on:PSA_WANT_ALG_CBC_PKCS7:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_decrypt:PSA_ALG_CBC_PKCS7:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"a076ec9dfbe47d52afc357336f20743ba3d6a86d0a9d172eeb1b754512d04416":"6bc1bee22e409f96e93d7e117393172a":1 diff --git a/tests/suites/test_suite_psa_crypto_constant_time.function b/tests/suites/test_suite_psa_crypto_constant_time.function new file mode 100644 index 0000000000..fb8368ad31 --- /dev/null +++ b/tests/suites/test_suite_psa_crypto_constant_time.function @@ -0,0 +1,161 @@ +/* BEGIN_HEADER */ +/* Positive test cases for PSA crypto APIs that assert constant-time + * (more accurately constant-flow) behavior. */ + +#include +#include + +/* Our software AES implementation is not constant-time. For constant-time + * testing involving AES, require a hardware-assisted AES that is + * constant-time. + * + * We assume that if the hardware-assisted version is available in the build, + * it will be available at runtime. The AES tests will fail if run on a + * processor without AESNI/AESCE. + */ +#include "aesce.h" +#include "aesni.h" +#if defined(MBEDTLS_AESCE_HAVE_CODE) || defined(MBEDTLS_AESNI_HAVE_CODE) +#define HAVE_CONSTANT_TIME_AES +#endif + +/* END_HEADER */ + +/* BEGIN_DEPENDENCIES + * depends_on:MBEDTLS_PSA_CRYPTO_C + * END_DEPENDENCIES + */ + +/* BEGIN_CASE */ +/* Known answer test for cipher multipart encryption. + * There is no known answer test for one-shot encryption because that + * uses a random IV. */ +void ct_cipher_encrypt(int alg_arg, + int key_type_arg, const data_t *key_data, + const data_t *iv, + const data_t *plaintext, + const data_t *expected_ciphertext) +{ + psa_key_type_t key_type = key_type_arg; + psa_algorithm_t alg = alg_arg; + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + mbedtls_svc_key_id_t key = MBEDTLS_SVC_KEY_ID_INIT; + psa_cipher_operation_t operation = PSA_CIPHER_OPERATION_INIT; + unsigned char *output = NULL; + size_t output_size = PSA_CIPHER_ENCRYPT_OUTPUT_MAX_SIZE(plaintext->len); + size_t update_length = SIZE_MAX; + size_t finish_length = SIZE_MAX; + + PSA_INIT(); + TEST_CALLOC(output, output_size); + TEST_CF_SECRET(key_data->x, key_data->len); + TEST_CF_SECRET(plaintext->x, plaintext->len); + //TEST_ASSERT(key_data->x[0] != 42); // uncomment to trip constant-flow test + + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT); + psa_set_key_algorithm(&attributes, alg); + psa_set_key_type(&attributes, key_type); + PSA_ASSERT(psa_import_key(&attributes, key_data->x, key_data->len, &key)); + + PSA_ASSERT(psa_cipher_encrypt_setup(&operation, key, alg)); + PSA_ASSERT(psa_cipher_set_iv(&operation, iv->x, iv->len)); + PSA_ASSERT(psa_cipher_update(&operation, + plaintext->x, plaintext->len, + output, output_size, &update_length)); + TEST_LE_U(update_length, output_size); + PSA_ASSERT(psa_cipher_finish(&operation, + output + update_length, + output_size - update_length, + &finish_length)); + + TEST_CF_PUBLIC(output, output_size); + TEST_MEMORY_COMPARE(expected_ciphertext->x, expected_ciphertext->len, + output, update_length + finish_length); + +exit: + mbedtls_free(output); + psa_cipher_abort(&operation); + psa_destroy_key(key); + PSA_DONE(); +} +/* END_CASE */ + +/* BEGIN_CASE */ +/* Known answer for cipher decryption (one-shot and multipart). + * Supports good cases and invalid padding cases. */ +void ct_cipher_decrypt(int alg_arg, + int key_type_arg, const data_t *key_data, + const data_t *iv, + const data_t *ciphertext, + const data_t *expected_plaintext, + int expect_invalid_padding) +{ + psa_key_type_t key_type = key_type_arg; + psa_algorithm_t alg = alg_arg; + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + mbedtls_svc_key_id_t key = MBEDTLS_SVC_KEY_ID_INIT; + psa_cipher_operation_t operation = PSA_CIPHER_OPERATION_INIT; + unsigned char *input = NULL; + unsigned char *output = NULL; + size_t output_size = PSA_CIPHER_DECRYPT_OUTPUT_MAX_SIZE(ciphertext->len); + size_t update_length = SIZE_MAX; + size_t finish_length = SIZE_MAX; + size_t output_length = SIZE_MAX; + psa_status_t status; + + PSA_INIT(); + TEST_CALLOC(output, output_size); + TEST_CF_SECRET(key_data->x, key_data->len); + TEST_CF_SECRET(ciphertext->x, ciphertext->len); + //TEST_ASSERT(key_data->x[0] != 42); // uncomment to trip constant-flow test + + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&attributes, alg); + psa_set_key_type(&attributes, key_type); + PSA_ASSERT(psa_import_key(&attributes, key_data->x, key_data->len, &key)); + + PSA_ASSERT(psa_cipher_decrypt_setup(&operation, key, alg)); + PSA_ASSERT(psa_cipher_set_iv(&operation, iv->x, iv->len)); + PSA_ASSERT(psa_cipher_update(&operation, + ciphertext->x, ciphertext->len, + output, output_size, &update_length)); + TEST_LE_U(update_length, output_size); + status = psa_cipher_finish(&operation, + output + update_length, + output_size - update_length, + &finish_length); + TEST_CF_PUBLIC(output, output_size); + + if (expect_invalid_padding) { + TEST_EQUAL(status, PSA_ERROR_INVALID_PADDING); + } else { + TEST_EQUAL(status, PSA_SUCCESS); + TEST_MEMORY_COMPARE(expected_plaintext->x, expected_plaintext->len, + output, update_length + finish_length); + } + + memset(output, 0, output_size); + TEST_CALLOC(input, iv->len + ciphertext->len); + memcpy(input, iv->x, iv->len); + memcpy(input + iv->len, ciphertext->x, ciphertext->len); + status = psa_cipher_decrypt(key, alg, + input, iv->len + ciphertext->len, + output, output_size, &output_length); + TEST_CF_PUBLIC(output, output_size); + + if (expect_invalid_padding) { + TEST_EQUAL(status, PSA_ERROR_INVALID_PADDING); + } else { + TEST_EQUAL(status, PSA_SUCCESS); + TEST_MEMORY_COMPARE(expected_plaintext->x, expected_plaintext->len, + output, output_length); + } + +exit: + mbedtls_free(input); + mbedtls_free(output); + psa_cipher_abort(&operation); + psa_destroy_key(key); + PSA_DONE(); +} +/* END_CASE */ From b6b1a8299b666fb827093de24c54e5788e8075dc Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 7 Aug 2025 20:28:34 +0200 Subject: [PATCH 81/91] Factor API calls into auxiliary functions Factor some common code for one-shot or multipart encryption/decryption into auxiliary functions. No behavior change. Signed-off-by: Gilles Peskine --- ...st_suite_psa_crypto_constant_time.function | 163 +++++++++++------- 1 file changed, 102 insertions(+), 61 deletions(-) diff --git a/tests/suites/test_suite_psa_crypto_constant_time.function b/tests/suites/test_suite_psa_crypto_constant_time.function index fb8368ad31..c212ec7250 100644 --- a/tests/suites/test_suite_psa_crypto_constant_time.function +++ b/tests/suites/test_suite_psa_crypto_constant_time.function @@ -19,6 +19,83 @@ #define HAVE_CONSTANT_TIME_AES #endif +static int ct_cipher_multipart(psa_cipher_operation_t *operation, + const data_t *iv, + const data_t *input, + size_t output_size, + const data_t *expected_output, + psa_status_t expected_finish_status) +{ + unsigned char *output = NULL; + size_t update_length = SIZE_MAX; + size_t finish_length = SIZE_MAX; + psa_status_t status; + int ok = 0; + + TEST_CALLOC(output, output_size); + + PSA_ASSERT(psa_cipher_set_iv(operation, iv->x, iv->len)); + status = psa_cipher_update(operation, + input->x, input->len, + output, output_size, &update_length); + if (expected_finish_status == PSA_ERROR_BUFFER_TOO_SMALL && + status == PSA_ERROR_BUFFER_TOO_SMALL) { + /* The output buffer is already too small for update. That's ok. */ + ok = 1; + goto exit; + } else { + PSA_ASSERT(status); + } + TEST_LE_U(update_length, output_size); + TEST_EQUAL(psa_cipher_finish(operation, + output + update_length, + output_size - update_length, + &finish_length), + expected_finish_status); + + TEST_CF_PUBLIC(output, output_size); + if (expected_finish_status == PSA_SUCCESS) { + TEST_MEMORY_COMPARE(expected_output->x, expected_output->len, + output, update_length + finish_length); + } + ok = 1; + +exit: + mbedtls_free(output); + psa_cipher_abort(operation); + return ok; +} + +static int ct_cipher_decrypt_oneshot(mbedtls_svc_key_id_t key, + psa_algorithm_t alg, + const data_t *input, + size_t output_size, + const data_t *expected_output, + psa_status_t expected_status) +{ + unsigned char *output = NULL; + size_t output_length = SIZE_MAX; + int ok = 0; + + TEST_CALLOC(output, output_size); + + TEST_EQUAL(psa_cipher_decrypt(key, alg, + input->x, input->len, + output, output_size, &output_length), + expected_status); + + TEST_CF_PUBLIC(output, output_size); + if (expected_status == PSA_SUCCESS) { + TEST_MEMORY_COMPARE(expected_output->x, expected_output->len, + output, output_length); + } + ok = 1; + +exit: + mbedtls_free(output); + return ok; +} + /* END_HEADER */ /* BEGIN_DEPENDENCIES @@ -41,13 +118,8 @@ void ct_cipher_encrypt(int alg_arg, psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; mbedtls_svc_key_id_t key = MBEDTLS_SVC_KEY_ID_INIT; psa_cipher_operation_t operation = PSA_CIPHER_OPERATION_INIT; - unsigned char *output = NULL; - size_t output_size = PSA_CIPHER_ENCRYPT_OUTPUT_MAX_SIZE(plaintext->len); - size_t update_length = SIZE_MAX; - size_t finish_length = SIZE_MAX; PSA_INIT(); - TEST_CALLOC(output, output_size); TEST_CF_SECRET(key_data->x, key_data->len); TEST_CF_SECRET(plaintext->x, plaintext->len); //TEST_ASSERT(key_data->x[0] != 42); // uncomment to trip constant-flow test @@ -58,22 +130,14 @@ void ct_cipher_encrypt(int alg_arg, PSA_ASSERT(psa_import_key(&attributes, key_data->x, key_data->len, &key)); PSA_ASSERT(psa_cipher_encrypt_setup(&operation, key, alg)); - PSA_ASSERT(psa_cipher_set_iv(&operation, iv->x, iv->len)); - PSA_ASSERT(psa_cipher_update(&operation, - plaintext->x, plaintext->len, - output, output_size, &update_length)); - TEST_LE_U(update_length, output_size); - PSA_ASSERT(psa_cipher_finish(&operation, - output + update_length, - output_size - update_length, - &finish_length)); - - TEST_CF_PUBLIC(output, output_size); - TEST_MEMORY_COMPARE(expected_ciphertext->x, expected_ciphertext->len, - output, update_length + finish_length); + if (!ct_cipher_multipart(&operation, iv, plaintext, + PSA_CIPHER_ENCRYPT_OUTPUT_MAX_SIZE(plaintext->len), + expected_ciphertext, + PSA_SUCCESS)) { + goto exit; + } exit: - mbedtls_free(output); psa_cipher_abort(&operation); psa_destroy_key(key); PSA_DONE(); @@ -92,19 +156,16 @@ void ct_cipher_decrypt(int alg_arg, { psa_key_type_t key_type = key_type_arg; psa_algorithm_t alg = alg_arg; + size_t sufficient_output_size = + PSA_CIPHER_DECRYPT_OUTPUT_SIZE(key_type, alg, ciphertext->len); + psa_status_t expected_status = + expect_invalid_padding ? PSA_ERROR_INVALID_PADDING : PSA_SUCCESS; psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; mbedtls_svc_key_id_t key = MBEDTLS_SVC_KEY_ID_INIT; psa_cipher_operation_t operation = PSA_CIPHER_OPERATION_INIT; - unsigned char *input = NULL; - unsigned char *output = NULL; - size_t output_size = PSA_CIPHER_DECRYPT_OUTPUT_MAX_SIZE(ciphertext->len); - size_t update_length = SIZE_MAX; - size_t finish_length = SIZE_MAX; - size_t output_length = SIZE_MAX; - psa_status_t status; + data_t input = { NULL, iv->len + ciphertext->len }; PSA_INIT(); - TEST_CALLOC(output, output_size); TEST_CF_SECRET(key_data->x, key_data->len); TEST_CF_SECRET(ciphertext->x, ciphertext->len); //TEST_ASSERT(key_data->x[0] != 42); // uncomment to trip constant-flow test @@ -115,45 +176,25 @@ void ct_cipher_decrypt(int alg_arg, PSA_ASSERT(psa_import_key(&attributes, key_data->x, key_data->len, &key)); PSA_ASSERT(psa_cipher_decrypt_setup(&operation, key, alg)); - PSA_ASSERT(psa_cipher_set_iv(&operation, iv->x, iv->len)); - PSA_ASSERT(psa_cipher_update(&operation, - ciphertext->x, ciphertext->len, - output, output_size, &update_length)); - TEST_LE_U(update_length, output_size); - status = psa_cipher_finish(&operation, - output + update_length, - output_size - update_length, - &finish_length); - TEST_CF_PUBLIC(output, output_size); - - if (expect_invalid_padding) { - TEST_EQUAL(status, PSA_ERROR_INVALID_PADDING); - } else { - TEST_EQUAL(status, PSA_SUCCESS); - TEST_MEMORY_COMPARE(expected_plaintext->x, expected_plaintext->len, - output, update_length + finish_length); + if (!ct_cipher_multipart(&operation, iv, ciphertext, + PSA_CIPHER_DECRYPT_OUTPUT_MAX_SIZE(ciphertext->len), + expected_plaintext, + expected_status)) { + goto exit; } - memset(output, 0, output_size); - TEST_CALLOC(input, iv->len + ciphertext->len); - memcpy(input, iv->x, iv->len); - memcpy(input + iv->len, ciphertext->x, ciphertext->len); - status = psa_cipher_decrypt(key, alg, - input, iv->len + ciphertext->len, - output, output_size, &output_length); - TEST_CF_PUBLIC(output, output_size); - - if (expect_invalid_padding) { - TEST_EQUAL(status, PSA_ERROR_INVALID_PADDING); - } else { - TEST_EQUAL(status, PSA_SUCCESS); - TEST_MEMORY_COMPARE(expected_plaintext->x, expected_plaintext->len, - output, output_length); + TEST_CALLOC(input.x, input.len); + memcpy(input.x, iv->x, iv->len); + memcpy(input.x + iv->len, ciphertext->x, ciphertext->len); + if (!ct_cipher_decrypt_oneshot(key, alg, &input, + sufficient_output_size, + expected_plaintext, + expected_status)) { + goto exit; } exit: - mbedtls_free(input); - mbedtls_free(output); + mbedtls_free(input.x); psa_cipher_abort(&operation); psa_destroy_key(key); PSA_DONE(); From d3e182e7dac9619b3efd7fa3de3452a160e7d971 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 7 Aug 2025 21:25:23 +0200 Subject: [PATCH 82/91] Add BUFFER_TOO_SMALL testing Signed-off-by: Gilles Peskine --- ...st_suite_psa_crypto_constant_time.function | 120 +++++++++++++++++- 1 file changed, 114 insertions(+), 6 deletions(-) diff --git a/tests/suites/test_suite_psa_crypto_constant_time.function b/tests/suites/test_suite_psa_crypto_constant_time.function index c212ec7250..740b8d7d18 100644 --- a/tests/suites/test_suite_psa_crypto_constant_time.function +++ b/tests/suites/test_suite_psa_crypto_constant_time.function @@ -115,6 +115,8 @@ void ct_cipher_encrypt(int alg_arg, { psa_key_type_t key_type = key_type_arg; psa_algorithm_t alg = alg_arg; + size_t sufficient_output_size = + PSA_CIPHER_ENCRYPT_OUTPUT_SIZE(key_type, alg, plaintext->len); psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; mbedtls_svc_key_id_t key = MBEDTLS_SVC_KEY_ID_INIT; psa_cipher_operation_t operation = PSA_CIPHER_OPERATION_INIT; @@ -129,9 +131,51 @@ void ct_cipher_encrypt(int alg_arg, psa_set_key_type(&attributes, key_type); PSA_ASSERT(psa_import_key(&attributes, key_data->x, key_data->len, &key)); + /* Output buffer too small for the actual output */ + mbedtls_test_set_step(1); PSA_ASSERT(psa_cipher_encrypt_setup(&operation, key, alg)); if (!ct_cipher_multipart(&operation, iv, plaintext, - PSA_CIPHER_ENCRYPT_OUTPUT_MAX_SIZE(plaintext->len), + expected_ciphertext->len - 1, + expected_ciphertext, + PSA_ERROR_BUFFER_TOO_SMALL)) { + goto exit; + } + + if (expected_ciphertext->len < sufficient_output_size) { + /* For a buffer of intermediate size (between the actual output length + * and the guaranteed sufficient size), either PSA_SUCCESS or + * PSA_ERROR_BUFFER_TOO_SMALL is acceptable. Require what the our + * built-in implementation currently does. */ + psa_status_t intermediate_size_status = PSA_SUCCESS; + + /* Output buffer size just large enough for the actual output + * but less than the guaranteed sufficient size */ + mbedtls_test_set_step(2); + PSA_ASSERT(psa_cipher_encrypt_setup(&operation, key, alg)); + if (!ct_cipher_multipart(&operation, iv, plaintext, + expected_ciphertext->len, + expected_ciphertext, + intermediate_size_status)) { + goto exit; + } + + /* Output buffer size large enough for the actual output + * but one less than the guaranteed sufficient size */ + mbedtls_test_set_step(3); + PSA_ASSERT(psa_cipher_encrypt_setup(&operation, key, alg)); + if (!ct_cipher_multipart(&operation, iv, plaintext, + sufficient_output_size - 1, + expected_ciphertext, + intermediate_size_status)) { + goto exit; + } + } + + /* Guaranteed sufficient output buffer size */ + mbedtls_test_set_step(4); + PSA_ASSERT(psa_cipher_encrypt_setup(&operation, key, alg)); + if (!ct_cipher_multipart(&operation, iv, plaintext, + sufficient_output_size, expected_ciphertext, PSA_SUCCESS)) { goto exit; @@ -170,22 +214,86 @@ void ct_cipher_decrypt(int alg_arg, TEST_CF_SECRET(ciphertext->x, ciphertext->len); //TEST_ASSERT(key_data->x[0] != 42); // uncomment to trip constant-flow test + TEST_CALLOC(input.x, input.len); + memcpy(input.x, iv->x, iv->len); + memcpy(input.x + iv->len, ciphertext->x, ciphertext->len); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT); psa_set_key_algorithm(&attributes, alg); psa_set_key_type(&attributes, key_type); PSA_ASSERT(psa_import_key(&attributes, key_data->x, key_data->len, &key)); + /* Output buffer too small for the actual output */ + mbedtls_test_set_step(1); PSA_ASSERT(psa_cipher_decrypt_setup(&operation, key, alg)); if (!ct_cipher_multipart(&operation, iv, ciphertext, - PSA_CIPHER_DECRYPT_OUTPUT_MAX_SIZE(ciphertext->len), + expected_plaintext->len - 1, + expected_plaintext, + PSA_ERROR_BUFFER_TOO_SMALL)) { + goto exit; + } + if (!ct_cipher_decrypt_oneshot(key, alg, &input, + expected_plaintext->len - 1, + expected_plaintext, + PSA_ERROR_BUFFER_TOO_SMALL)) { + goto exit; + } + + if (expected_plaintext->len < sufficient_output_size) { + /* For a buffer of intermediate size (between the actual output length + * and the guaranteed sufficient size), either PSA_SUCCESS (or + * PSA_ERROR_INVALID_PADDING if the padding is invalid) or + * PSA_ERROR_BUFFER_TOO_SMALL is acceptable. Require what the our + * built-in implementation currently does. */ + psa_status_t intermediate_size_status = expected_status; + if (alg == PSA_ALG_CBC_PKCS7) { + intermediate_size_status = PSA_ERROR_BUFFER_TOO_SMALL; + } + + /* Output buffer size just large enough for the actual output + * but less than the guaranteed sufficient size */ + mbedtls_test_set_step(2); + PSA_ASSERT(psa_cipher_decrypt_setup(&operation, key, alg)); + if (!ct_cipher_multipart(&operation, iv, ciphertext, + expected_plaintext->len, + expected_plaintext, + intermediate_size_status)) { + goto exit; + } + if (!ct_cipher_decrypt_oneshot(key, alg, &input, + expected_plaintext->len - 1, + expected_plaintext, + intermediate_size_status)) { + goto exit; + } + + /* Output buffer size large enough for the actual output + * but one less than the guaranteed sufficient size */ + mbedtls_test_set_step(3); + PSA_ASSERT(psa_cipher_decrypt_setup(&operation, key, alg)); + if (!ct_cipher_multipart(&operation, iv, ciphertext, + sufficient_output_size - 1, + expected_plaintext, + intermediate_size_status)) { + goto exit; + } + if (!ct_cipher_decrypt_oneshot(key, alg, &input, + sufficient_output_size - 1, + expected_plaintext, + intermediate_size_status)) { + goto exit; + } + } + + /* Guaranteed sufficient output buffer size */ + mbedtls_test_set_step(4); + PSA_ASSERT(psa_cipher_decrypt_setup(&operation, key, alg)); + if (!ct_cipher_multipart(&operation, iv, ciphertext, + sufficient_output_size, expected_plaintext, expected_status)) { goto exit; } - - TEST_CALLOC(input.x, input.len); - memcpy(input.x, iv->x, iv->len); - memcpy(input.x + iv->len, ciphertext->x, ciphertext->len); if (!ct_cipher_decrypt_oneshot(key, alg, &input, sufficient_output_size, expected_plaintext, From d179dc80a5b13189c79fe4531eacb28698a7a0e9 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 27 Jul 2025 18:57:04 +0200 Subject: [PATCH 83/91] Use mbedtls_psa_cipher_finish() in PSA Signed-off-by: Gilles Peskine --- library/psa_crypto_cipher.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/library/psa_crypto_cipher.c b/library/psa_crypto_cipher.c index efc5813ff0..4443d73ba4 100644 --- a/library/psa_crypto_cipher.c +++ b/library/psa_crypto_cipher.c @@ -552,6 +552,7 @@ psa_status_t mbedtls_psa_cipher_finish( { psa_status_t status = PSA_ERROR_GENERIC_ERROR; uint8_t temp_output_buffer[MBEDTLS_MAX_BLOCK_LENGTH]; + size_t invalid_padding = 0; if (operation->ctx.cipher.unprocessed_len != 0) { if (operation->alg == PSA_ALG_ECB_NO_PADDING || @@ -562,9 +563,10 @@ psa_status_t mbedtls_psa_cipher_finish( } status = mbedtls_to_psa_error( - mbedtls_cipher_finish(&operation->ctx.cipher, - temp_output_buffer, - output_length)); + mbedtls_cipher_finish_padded(&operation->ctx.cipher, + temp_output_buffer, + output_length, + &invalid_padding)); if (status != PSA_SUCCESS) { goto exit; } @@ -581,6 +583,9 @@ exit: mbedtls_platform_zeroize(temp_output_buffer, sizeof(temp_output_buffer)); + if (status == PSA_SUCCESS && invalid_padding) { + status = PSA_ERROR_INVALID_PADDING; + } return status; } From e74b42832e4af11606ef8aae2c9404b4acaa2c6d Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 27 Jul 2025 21:29:40 +0200 Subject: [PATCH 84/91] Return PSA_ERROR_INVALID_PADDING in constant time Signed-off-by: Gilles Peskine --- library/psa_crypto_cipher.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/psa_crypto_cipher.c b/library/psa_crypto_cipher.c index 4443d73ba4..6d0378bd7e 100644 --- a/library/psa_crypto_cipher.c +++ b/library/psa_crypto_cipher.c @@ -13,6 +13,7 @@ #include "psa_crypto_cipher.h" #include "psa_crypto_core.h" #include "psa_crypto_random_impl.h" +#include "constant_time_internal.h" #include "mbedtls/cipher.h" #include "mbedtls/error.h" @@ -583,8 +584,9 @@ exit: mbedtls_platform_zeroize(temp_output_buffer, sizeof(temp_output_buffer)); - if (status == PSA_SUCCESS && invalid_padding) { - status = PSA_ERROR_INVALID_PADDING; + if (status == PSA_SUCCESS) { + status = mbedtls_ct_size_if_else_0(invalid_padding, + PSA_ERROR_INVALID_PADDING); } return status; } From 3b380daedbce9fae3e7ed7e84f18e97876e7e6f3 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 7 Aug 2025 21:59:07 +0200 Subject: [PATCH 85/91] psa_cipher_finish: treat status and output length as sensitive In `psa_cipher_finish()` and in the corresponding function in our built-in implementation `mbedtls_psa_cipher_finish()`, treat `status` and `*output_length` as sensitive variables whose value must not leak through a timing side channel. This is important when doing decryption with unpadding, where leaking the validity or amount of padding can enable a padding oracle attack. With this change, `psa_cipher_finish()` should be constant-time if the underlying legacy function (including the cipher implementation) is. Signed-off-by: Gilles Peskine --- library/psa_crypto.c | 26 +++++++++++++++++++++----- library/psa_crypto_cipher.c | 36 ++++++++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/library/psa_crypto.c b/library/psa_crypto.c index 9c28609d7e..51ec72fa19 100644 --- a/library/psa_crypto.c +++ b/library/psa_crypto.c @@ -73,6 +73,8 @@ #include "mbedtls/psa_util.h" #include "mbedtls/threading.h" +#include "constant_time_internal.h" + #if defined(MBEDTLS_PSA_BUILTIN_ALG_HKDF) || \ defined(MBEDTLS_PSA_BUILTIN_ALG_HKDF_EXTRACT) || \ defined(MBEDTLS_PSA_BUILTIN_ALG_HKDF_EXPAND) @@ -4692,13 +4694,27 @@ psa_status_t psa_cipher_finish(psa_cipher_operation_t *operation, output_length); exit: - if (status == PSA_SUCCESS) { - status = psa_cipher_abort(operation); - } else { - *output_length = 0; - (void) psa_cipher_abort(operation); + /* C99 doesn't allow a declaration to follow a label */; + psa_status_t abort_status = psa_cipher_abort(operation); + /* Normally abort shouldn't fail unless the operation is in a bad + * state, in which case we'd expect finish to fail with the same error. + * So it doesn't matter much which call's error code we pick when both + * fail. However, in unauthenticated decryption specifically, the + * distinction between PSA_SUCCESS and PSA_ERROR_INVALID_PADDING is + * security-sensitive (risk of a padding oracle attack), so here we + * must not have a code path that depends on the value of status. */ + if (abort_status != PSA_SUCCESS) { + status = abort_status; } + /* Set *output_length to 0 if status != PSA_SUCCESS, without + * leaking the value of status through a timing side channel + * (status == PSA_ERROR_INVALID_PADDING is sensitive when doing + * unpadded decryption, due to the risk of padding oracle attack). */ + mbedtls_ct_condition_t success = + mbedtls_ct_bool_not(mbedtls_ct_bool(status)); + *output_length = mbedtls_ct_size_if_else_0(success, *output_length); + LOCAL_OUTPUT_FREE(output_external, output); return status; diff --git a/library/psa_crypto_cipher.c b/library/psa_crypto_cipher.c index 6d0378bd7e..022bdfa49f 100644 --- a/library/psa_crypto_cipher.c +++ b/library/psa_crypto_cipher.c @@ -552,9 +552,21 @@ psa_status_t mbedtls_psa_cipher_finish( uint8_t *output, size_t output_size, size_t *output_length) { psa_status_t status = PSA_ERROR_GENERIC_ERROR; - uint8_t temp_output_buffer[MBEDTLS_MAX_BLOCK_LENGTH]; size_t invalid_padding = 0; + uint8_t temp_output_buffer[MBEDTLS_MAX_BLOCK_LENGTH] = { 0 }; + if (output_size > sizeof(temp_output_buffer)) { + output_size = sizeof(temp_output_buffer); + } + /* We will copy output_size bytes from temp_output_buffer to the + * output buffer. We can't use *output_length to determine how + * much to copy because we must not leak that value through timing + * when doing decryption with unpadding. But the underlying function + * is not guaranteed to write beyond *output_length. To ensure we don't + * leak the former content of the stack to the caller, wipe that + * former content. */ + memset(temp_output_buffer, 0, output_size); + if (operation->ctx.cipher.unprocessed_len != 0) { if (operation->alg == PSA_ALG_ECB_NO_PADDING || operation->alg == PSA_ALG_CBC_NO_PADDING) { @@ -572,22 +584,26 @@ psa_status_t mbedtls_psa_cipher_finish( goto exit; } - if (*output_length == 0) { + if (output_size == 0) { ; /* Nothing to copy. Note that output may be NULL in this case. */ - } else if (output_size >= *output_length) { - memcpy(output, temp_output_buffer, *output_length); } else { - status = PSA_ERROR_BUFFER_TOO_SMALL; + /* Do not use the value of *output_length to determine how much + * to copy. When decrypting a padded cipher, the output length is + * sensitive, and leaking it could allow a padding oracle attack. */ + memcpy(output, temp_output_buffer, output_size); } + status = mbedtls_ct_error_if_else_0(invalid_padding, + PSA_ERROR_INVALID_PADDING); + mbedtls_ct_condition_t buffer_too_small = + mbedtls_ct_uint_lt(output_size, *output_length); + status = mbedtls_ct_error_if(buffer_too_small, + PSA_ERROR_BUFFER_TOO_SMALL, + status); + exit: mbedtls_platform_zeroize(temp_output_buffer, sizeof(temp_output_buffer)); - - if (status == PSA_SUCCESS) { - status = mbedtls_ct_size_if_else_0(invalid_padding, - PSA_ERROR_INVALID_PADDING); - } return status; } From 04dfd704325a6dbc2a13eb7f418eaca9ae9ca549 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 7 Aug 2025 22:27:26 +0200 Subject: [PATCH 86/91] psa_cipher_decrypt: treat status and output length as sensitive In `psa_cipher_decrypt()` and in the corresponding function in our built-in implementation `mbedtls_psa_cipher_decrypt()`, treat `status` and `*output_length` as sensitive variables whose value must not leak through a timing side channel. This is important when doing decryption with unpadding, where leaking the validity or amount of padding can enable a padding oracle attack. With this change, `psa_cipher_decrypt()` should be constant-time if the underlying legacy function (including the cipher implementation) is. Signed-off-by: Gilles Peskine --- library/psa_crypto.c | 12 ++++++++---- library/psa_crypto_cipher.c | 18 +++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/library/psa_crypto.c b/library/psa_crypto.c index 51ec72fa19..5f2cad42b7 100644 --- a/library/psa_crypto.c +++ b/library/psa_crypto.c @@ -4857,13 +4857,17 @@ psa_status_t psa_cipher_decrypt(mbedtls_svc_key_id_t key, exit: unlock_status = psa_unregister_read_under_mutex(slot); - if (status == PSA_SUCCESS) { + if (unlock_status != PSA_SUCCESS) { status = unlock_status; } - if (status != PSA_SUCCESS) { - *output_length = 0; - } + /* Set *output_length to 0 if status != PSA_SUCCESS, without + * leaking the value of status through a timing side channel + * (status == PSA_ERROR_INVALID_PADDING is sensitive when doing + * unpadded decryption, due to the risk of padding oracle attack). */ + mbedtls_ct_condition_t success = + mbedtls_ct_bool_not(mbedtls_ct_bool(status)); + *output_length = mbedtls_ct_size_if_else_0(success, *output_length); LOCAL_INPUT_FREE(input_external, input); LOCAL_OUTPUT_FREE(output_external, output); diff --git a/library/psa_crypto_cipher.c b/library/psa_crypto_cipher.c index 022bdfa49f..83069c7852 100644 --- a/library/psa_crypto_cipher.c +++ b/library/psa_crypto_cipher.c @@ -724,17 +724,21 @@ psa_status_t mbedtls_psa_cipher_decrypt( &operation, mbedtls_buffer_offset(output, accumulated_length), output_size - accumulated_length, &olength); - if (status != PSA_SUCCESS) { - goto exit; - } *output_length = accumulated_length + olength; exit: - if (status == PSA_SUCCESS) { - status = mbedtls_psa_cipher_abort(&operation); - } else { - mbedtls_psa_cipher_abort(&operation); + /* C99 doesn't allow a declaration to follow a label */; + psa_status_t abort_status = mbedtls_psa_cipher_abort(&operation); + /* Normally abort shouldn't fail unless the operation is in a bad + * state, in which case we'd expect finish to fail with the same error. + * So it doesn't matter much which call's error code we pick when both + * fail. However, in unauthenticated decryption specifically, the + * distinction between PSA_SUCCESS and PSA_ERROR_INVALID_PADDING is + * security-sensitive (risk of a padding oracle attack), so here we + * must not have a code path that depends on the value of status. */ + if (abort_status != PSA_SUCCESS) { + status = abort_status; } return status; From 2d666646bace4de2fce0f8dd9f95df560dd55f73 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 7 Aug 2025 23:07:31 +0200 Subject: [PATCH 87/91] Changelog entry for PSA CBC-PKCS7 padding oracle fix Signed-off-by: Gilles Peskine --- ChangeLog.d/pkcs7-padding-error-leak.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 ChangeLog.d/pkcs7-padding-error-leak.txt diff --git a/ChangeLog.d/pkcs7-padding-error-leak.txt b/ChangeLog.d/pkcs7-padding-error-leak.txt new file mode 100644 index 0000000000..5d204d5bef --- /dev/null +++ b/ChangeLog.d/pkcs7-padding-error-leak.txt @@ -0,0 +1,5 @@ +Security + * Fix a timing side channel in CBC-PKCS7 decryption that could + allow an attacker who can submit chosen ciphertexts to recover + some plaintexts through a timing-based padding oracle attack. + Credits to Beat Heeb from Oberon microsystems AG. CVE-TODO From cc908ad04c388b50b81fa3b3a8b509cf62797fcf Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 25 Aug 2025 17:01:34 +0200 Subject: [PATCH 88/91] Remove redundant memset on freshly initialized buffer Signed-off-by: Gilles Peskine --- library/psa_crypto_cipher.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/library/psa_crypto_cipher.c b/library/psa_crypto_cipher.c index 83069c7852..7f691c1d95 100644 --- a/library/psa_crypto_cipher.c +++ b/library/psa_crypto_cipher.c @@ -554,10 +554,6 @@ psa_status_t mbedtls_psa_cipher_finish( psa_status_t status = PSA_ERROR_GENERIC_ERROR; size_t invalid_padding = 0; - uint8_t temp_output_buffer[MBEDTLS_MAX_BLOCK_LENGTH] = { 0 }; - if (output_size > sizeof(temp_output_buffer)) { - output_size = sizeof(temp_output_buffer); - } /* We will copy output_size bytes from temp_output_buffer to the * output buffer. We can't use *output_length to determine how * much to copy because we must not leak that value through timing @@ -565,7 +561,10 @@ psa_status_t mbedtls_psa_cipher_finish( * is not guaranteed to write beyond *output_length. To ensure we don't * leak the former content of the stack to the caller, wipe that * former content. */ - memset(temp_output_buffer, 0, output_size); + uint8_t temp_output_buffer[MBEDTLS_MAX_BLOCK_LENGTH] = { 0 }; + if (output_size > sizeof(temp_output_buffer)) { + output_size = sizeof(temp_output_buffer); + } if (operation->ctx.cipher.unprocessed_len != 0) { if (operation->alg == PSA_ALG_ECB_NO_PADDING || From c6b28b31ef5ad106e34102bf084b5c81cedc61a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 11 Sep 2025 09:58:45 +0200 Subject: [PATCH 89/91] Be explicit about modinv output range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/bignum_internal.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/library/bignum_internal.h b/library/bignum_internal.h index 341346222d..ba1c69d6b1 100644 --- a/library/bignum_internal.h +++ b/library/bignum_internal.h @@ -63,7 +63,8 @@ int mbedtls_mpi_exp_mod_unsafe(mbedtls_mpi *X, const mbedtls_mpi *A, * \param[out] G The GCD of \p A and \p N. * This may be NULL, to only compute I. * \param[out] I The inverse of \p A modulo \p N if it exists (that is, - * if \p G above is 1 on exit); indeterminate otherwise. + * if \p G above is 1 on exit), in the range [1, \p N); + * indeterminate otherwise. * This may be NULL, to only compute G. * \param[in] A The 1st operand of GCD and number to invert. * This value must be less than or equal to \p N. @@ -83,8 +84,8 @@ int mbedtls_mpi_gcd_modinv_odd(mbedtls_mpi *G, /** * \brief Modular inverse: X = A^-1 mod N with N odd * - * \param[out] X The inverse of \p A modulo \p N on success, - * indeterminate otherwise. + * \param[out] X The inverse of \p A modulo \p N in the range [1, \p N) + * on success; indeterminate otherwise. * \param[in] A The number to invert. * \param[in] N The modulus. Must be odd and greater than 1. * @@ -102,8 +103,8 @@ int mbedtls_mpi_inv_mod_odd(mbedtls_mpi *X, * \brief Modular inverse: X = A^-1 mod N with N even, * A odd and 1 < A < N. * - * \param[out] X The inverse of \p A modulo \p N on success, - * indeterminate otherwise. + * \param[out] X The inverse of \p A modulo \p N in the range [1, \p N) + * on success; indeterminate otherwise. * \param[in] A The number to invert. Must be odd, greated than 1 * and less than \p N. * \param[in] N The modulus. Must be even and greater than 1. From d1244932f11502e51a259cc9dfbcfb52200b8c8a Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 16 Sep 2025 10:39:29 +0200 Subject: [PATCH 90/91] We have a CVE ID Signed-off-by: Gilles Peskine --- ChangeLog.d/pkcs7-padding-error-leak.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.d/pkcs7-padding-error-leak.txt b/ChangeLog.d/pkcs7-padding-error-leak.txt index 5d204d5bef..9193a0860e 100644 --- a/ChangeLog.d/pkcs7-padding-error-leak.txt +++ b/ChangeLog.d/pkcs7-padding-error-leak.txt @@ -2,4 +2,4 @@ Security * Fix a timing side channel in CBC-PKCS7 decryption that could allow an attacker who can submit chosen ciphertexts to recover some plaintexts through a timing-based padding oracle attack. - Credits to Beat Heeb from Oberon microsystems AG. CVE-TODO + Credits to Beat Heeb from Oberon microsystems AG. CVE-2025-59438 From aa611e4bef3d18d45f775ac25ab0c50e935107a9 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 17 Sep 2025 18:22:30 +0200 Subject: [PATCH 91/91] Update framework to the merge of the merge PR Signed-off-by: Gilles Peskine --- framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework b/framework index 82a7962c5f..4f962bfcb3 160000 --- a/framework +++ b/framework @@ -1 +1 @@ -Subproject commit 82a7962c5f7cbe6e8a60c239cbb477ee06f94182 +Subproject commit 4f962bfcb30f565e7c995366b13fc8ec6194a0d2