From 6373fab865410533a422d715ceabb5d60420b3f7 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 29 Mar 2021 21:32:16 +0200 Subject: [PATCH] mbedtls_ecp_gen_privkey_sw: range and coverage tests Add unit tests for private key generation on short Weierstrass curves. These tests validate that the result is within the desired range. Additionally, they validate that after performing many iterations, the range is covered to an acceptable extent: for tiny ranges, all values must be reached; for larger ranges, all value bits must reach both 0 and 1. Signed-off-by: Gilles Peskine --- tests/suites/test_suite_ecp.data | 90 +++++++++++++++++ tests/suites/test_suite_ecp.function | 145 +++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) diff --git a/tests/suites/test_suite_ecp.data b/tests/suites/test_suite_ecp.data index 5f92ca459..0c344561b 100644 --- a/tests/suites/test_suite_ecp.data +++ b/tests/suites/test_suite_ecp.data @@ -312,6 +312,96 @@ genkey_mx_known_answer:447:"ffffffffffffffffffffffffffffffffffffffffffffffffffff ECP generate Montgomery key: Curve448, not enough entropy genkey_mx_known_answer:447:"4f0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536":"" +ECP generate in range: 4 +genkey_sw_many:"04":1000 + +ECP generate in range: 5 +genkey_sw_many:"05":1000 + +ECP generate in range: 6 +genkey_sw_many:"06":1000 + +ECP generate in range: 7 +genkey_sw_many:"07":1000 + +ECP generate in range: 8 +genkey_sw_many:"08":1000 + +ECP generate in range: 9 +genkey_sw_many:"09":1000 + +ECP generate in range: 10 +genkey_sw_many:"0a":1000 + +ECP generate in range: 11 +genkey_sw_many:"0b":1000 + +ECP generate in range: 12 +genkey_sw_many:"0c":1000 + +ECP generate in range: 255 +genkey_sw_many:"ff":100 + +ECP generate in range: 256 +genkey_sw_many:"0100":100 + +ECP generate in range: 257 +genkey_sw_many:"0101":100 + +ECP generate in range: 272 +genkey_sw_many:"0110":100 + +ECP generate in range: 2^64-1 +genkey_sw_many:"ffffffffffffffff":100 + +ECP generate in range: 2^64 +genkey_sw_many:"010000000000000000":100 + +ECP generate in range: 2^64+1 +genkey_sw_many:"010000000000000001":100 + +ECP generate in range: 2^64+2^63 +genkey_sw_many:"018000000000000000":100 + +ECP generate in range: 2^65-1 +genkey_sw_many:"01ffffffffffffffff":100 + +ECP generate in range: 2^65 +genkey_sw_many:"020000000000000000":100 + +ECP generate in range: 2^65+1 +genkey_sw_many:"020000000000000001":100 + +ECP generate in range: 2^65+2^64 +genkey_sw_many:"030000000000000000":100 + +ECP generate in range: 2^66+2^65 +genkey_sw_many:"060000000000000000":100 + +ECP generate in range: 2^71-1 +genkey_sw_many:"7fffffffffffffffff":100 + +ECP generate in range: 2^71 +genkey_sw_many:"800000000000000000":100 + +ECP generate in range: 2^71+1 +genkey_sw_many:"800000000000000001":100 + +ECP generate in range: 2^71+2^63 +genkey_sw_many:"c00000000000000000":100 + +ECP generate in range: 2^72-1 +genkey_sw_many:"ffffffffffffffffff":100 + +ECP generate in range: 2^72 +genkey_sw_many:"01000000000000000000":100 + +ECP generate in range: 2^72+1 +genkey_sw_many:"01000000000000000001":100 + +ECP generate in range: 2^72+2^63 +genkey_sw_many:"01800000000000000000":100 + ECP read key #1 (short weierstrass, too small) depends_on:MBEDTLS_ECP_DP_SECP192R1_ENABLED mbedtls_ecp_read_key:MBEDTLS_ECP_DP_SECP192R1:"00":MBEDTLS_ERR_ECP_INVALID_KEY:0 diff --git a/tests/suites/test_suite_ecp.function b/tests/suites/test_suite_ecp.function index 1492b9531..1049c96a1 100644 --- a/tests/suites/test_suite_ecp.function +++ b/tests/suites/test_suite_ecp.function @@ -15,6 +15,43 @@ #define ECP_PT_RESET( x ) \ mbedtls_ecp_point_free( x ); \ mbedtls_ecp_point_init( x ); + +#if defined(MBEDTLS_TEST_HOOKS) && defined(MBEDTLS_ECP_SHORT_WEIERSTRASS_ENABLED) +/* Test whether bytes represents (in big-endian base 256) a number B that + * is "significantly" above a power of 2, which is defined as follows. + * Let n be the integer such that 2^n <= B < 2^{n+1}. B is significantly + * above a power of 2 if (B - 2^n) / 2^n is not negligible. "Negligible" + * is defined as having a negligible chance that if you draw an integer + * in the range [1, B-1] K times, the number will always be less than 2^n, + * where K is the iteration count passed to genkey_sw_many. + */ +static int is_significantly_above_a_power_of_2( data_t *bytes ) +{ + const uint8_t *p = bytes->x; + size_t len = bytes->len; + unsigned x; + while( len > 0 && p[0] == 0 ) + { + ++p; + --len; + } + if( len == 0 ) + return( 0 ); + else if( len == 1 ) + x = p[0]; + else + x = ( p[0] << 8 ) | p[1]; + + if( x <= 4 ) + return( 0 ); + + while( ( x & 0x8000 ) == 0 ) + x <<= 1; + x &= 0x7fff; + return( x >= 0x1000 ); +} +#endif + /* END_HEADER */ /* BEGIN_DEPENDENCIES @@ -1286,6 +1323,114 @@ exit: } /* END_CASE */ +/* BEGIN_CASE depends_on:MBEDTLS_TEST_HOOKS:MBEDTLS_ECP_SHORT_WEIERSTRASS_ENABLED */ +void genkey_sw_many( data_t *bound_bytes, int iterations ) +{ + /* Generate numbers in the range 1..bound-1. Do it iterations times. + * This function assumes that the value of bound is at least 2 and + * that iterations is large enough that a one-in-2^iterations chance + * effectively never occurs. + */ + + mbedtls_mpi bound; + size_t n_bits; + mbedtls_mpi result; + size_t b; + /* If bound is small, stats[b] is the number of times the value b + * has been generated. Otherwise stats[b] is the number of times a + * value with bit b set has been generated. */ + size_t *stats = NULL; + size_t stats_len; + int full_stats; + size_t i; + + mbedtls_mpi_init( &bound ); + mbedtls_mpi_init( &result ); + + TEST_EQUAL( 0, mbedtls_mpi_read_binary( &bound, + bound_bytes->x, bound_bytes->len ) ); + n_bits = mbedtls_mpi_bitlen( &bound ); + /* Consider a bound "small" if it's less than 2^5. This value is chosen + * to be small enough that the probability of missing one value is + * negligible given the number of iterations. It must be less than + * 256 because some of the code below assumes that "small" values + * fit in a byte. */ + if( n_bits <= 5 ) + { + full_stats = 1; + stats_len = bound_bytes->x[bound_bytes->len - 1]; + } + else + { + full_stats = 0; + stats_len = n_bits; + } + ASSERT_ALLOC( stats, stats_len ); + + for( i = 0; i < (size_t) iterations; i++ ) + { + mbedtls_test_set_step( i ); + TEST_EQUAL( 0, mbedtls_ecp_gen_privkey_sw( + &bound, n_bits, &result, + mbedtls_test_rnd_std_rand, NULL ) ); + + TEST_ASSERT( mbedtls_mpi_cmp_mpi( &result, &bound ) < 0 ); + TEST_ASSERT( mbedtls_mpi_cmp_int( &result, 1 ) >= 0 ); + if( full_stats ) + { + uint8_t value; + TEST_EQUAL( 0, mbedtls_mpi_write_binary( &result, &value, 1 ) ); + TEST_ASSERT( value < stats_len ); + ++stats[value]; + } + else + { + for( b = 0; b < n_bits; b++ ) + stats[b] += mbedtls_mpi_get_bit( &result, b ); + } + } + + if( full_stats ) + { + for( b = 1; b < stats_len; b++ ) + { + mbedtls_test_set_step( 1000000 + b ); + /* Assert that each value has been reached at least once. + * This is almost guaranteed if the iteration count is large + * enough. This is a very crude way of checking the distribution. + */ + TEST_ASSERT( stats[b] > 0 ); + } + } + else + { + for( b = 0; b < n_bits; b++ ) + { + mbedtls_test_set_step( 1000000 + b ); + /* Assert that each bit has been set in at least one result and + * clear in at least one result. Provided that iterations is not + * too small, it would be extremely unlikely for this not to be + * the case if the results are uniformly distributed. + * + * As an exception, the top bit may legitimately never be set + * if bound is a power of 2 or only slightly above. + */ + if( b != n_bits - 1 || + is_significantly_above_a_power_of_2( bound_bytes ) ) + { + TEST_ASSERT( stats[b] > 0 ); + } + TEST_ASSERT( stats[b] < (size_t) iterations ); + } + } + +exit: + mbedtls_mpi_free( &bound ); + mbedtls_mpi_free( &result ); + mbedtls_free( stats ); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_SELF_TEST */ void ecp_selftest( ) {