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 <Gilles.Peskine@arm.com>
This commit is contained in:
Gilles Peskine 2021-03-29 21:32:16 +02:00
parent eadf31d56a
commit 6373fab865
2 changed files with 235 additions and 0 deletions

View file

@ -312,6 +312,96 @@ genkey_mx_known_answer:447:"ffffffffffffffffffffffffffffffffffffffffffffffffffff
ECP generate Montgomery key: Curve448, not enough entropy ECP generate Montgomery key: Curve448, not enough entropy
genkey_mx_known_answer:447:"4f0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536":"" 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) ECP read key #1 (short weierstrass, too small)
depends_on:MBEDTLS_ECP_DP_SECP192R1_ENABLED depends_on:MBEDTLS_ECP_DP_SECP192R1_ENABLED
mbedtls_ecp_read_key:MBEDTLS_ECP_DP_SECP192R1:"00":MBEDTLS_ERR_ECP_INVALID_KEY:0 mbedtls_ecp_read_key:MBEDTLS_ECP_DP_SECP192R1:"00":MBEDTLS_ERR_ECP_INVALID_KEY:0

View file

@ -15,6 +15,43 @@
#define ECP_PT_RESET( x ) \ #define ECP_PT_RESET( x ) \
mbedtls_ecp_point_free( x ); \ mbedtls_ecp_point_free( x ); \
mbedtls_ecp_point_init( 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 */ /* END_HEADER */
/* BEGIN_DEPENDENCIES /* BEGIN_DEPENDENCIES
@ -1286,6 +1323,114 @@ exit:
} }
/* END_CASE */ /* 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 */ /* BEGIN_CASE depends_on:MBEDTLS_SELF_TEST */
void ecp_selftest( ) void ecp_selftest( )
{ {