Merged more constant-time checking in RSA
diff --git a/ChangeLog b/ChangeLog
index 7bf2a67..9419df2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -6,6 +6,7 @@
* Speedup of ECP multiplication operation
* Relaxed some SHA2 ciphersuite's version requirements
* Dropped use of readdir_r() instead of readdir() with threading support
+ * More constant-time checks in the RSA module
Bugfix
* Fixed X.509 hostname comparison (with non-regular characters)
diff --git a/library/rsa.c b/library/rsa.c
index 210ea46..fdcfaef 100644
--- a/library/rsa.c
+++ b/library/rsa.c
@@ -646,14 +646,17 @@
size_t output_max_len )
{
int ret;
- size_t ilen;
- unsigned char *p;
+ size_t ilen, i, pad_len;
+ unsigned char *p, bad, pad_done;
unsigned char buf[POLARSSL_MPI_MAX_SIZE];
unsigned char lhash[POLARSSL_MD_MAX_SIZE];
unsigned int hlen;
const md_info_t *md_info;
md_context_t md_ctx;
+ /*
+ * Parameters sanity checks
+ */
if( ctx->padding != RSA_PKCS_V21 )
return( POLARSSL_ERR_RSA_BAD_INPUT_DATA );
@@ -662,6 +665,13 @@
if( ilen < 16 || ilen > sizeof( buf ) )
return( POLARSSL_ERR_RSA_BAD_INPUT_DATA );
+ md_info = md_info_from_type( ctx->hash_id );
+ if( md_info == NULL )
+ return( POLARSSL_ERR_RSA_BAD_INPUT_DATA );
+
+ /*
+ * RSA operation
+ */
ret = ( mode == RSA_PUBLIC )
? rsa_public( ctx, input, buf )
: rsa_private( ctx, f_rng, p_rng, input, buf );
@@ -669,50 +679,60 @@
if( ret != 0 )
return( ret );
- p = buf;
-
- if( *p++ != 0 )
- return( POLARSSL_ERR_RSA_INVALID_PADDING );
-
- md_info = md_info_from_type( ctx->hash_id );
- if( md_info == NULL )
- return( POLARSSL_ERR_RSA_BAD_INPUT_DATA );
-
+ /*
+ * Unmask data and generate lHash
+ */
hlen = md_get_size( md_info );
md_init_ctx( &md_ctx, md_info );
- // Generate lHash
- //
+ /* Generate lHash */
md( md_info, label, label_len, lhash );
- // seed: Apply seedMask to maskedSeed
- //
+ /* seed: Apply seedMask to maskedSeed */
mgf_mask( buf + 1, hlen, buf + hlen + 1, ilen - hlen - 1,
&md_ctx );
- // DB: Apply dbMask to maskedDB
- //
+ /* DB: Apply dbMask to maskedDB */
mgf_mask( buf + hlen + 1, ilen - hlen - 1, buf + 1, hlen,
&md_ctx );
- p += hlen;
md_free_ctx( &md_ctx );
- // Check validity
- //
- if( memcmp( lhash, p, hlen ) != 0 )
- return( POLARSSL_ERR_RSA_INVALID_PADDING );
+ /*
+ * Check contents, in "constant-time"
+ */
+ p = buf;
+ bad = 0;
- p += hlen;
+ bad |= *p++; /* First byte must be 0 */
- while( *p == 0 && p < buf + ilen )
- p++;
+ p += hlen; /* Skip seed */
- if( p == buf + ilen )
- return( POLARSSL_ERR_RSA_INVALID_PADDING );
+ /* Check lHash */
+ for( i = 0; i < hlen; i++ )
+ bad |= lhash[i] ^ *p++;
- if( *p++ != 0x01 )
+ /* Get zero-padding len, but always read till end of buffer
+ * (minus one, for the 01 byte) */
+ pad_len = 0;
+ pad_done = 0;
+ for( i = 0; i < ilen - 2 * hlen - 2; i++ )
+ {
+ pad_done |= p[i];
+ pad_len += ( pad_done == 0 );
+ }
+
+ p += pad_len;
+ bad |= *p++ ^ 0x01;
+
+ /*
+ * The only information "leaked" is whether the padding was correct or not
+ * (eg, no data is copied if it was not correct). This meets the
+ * recommendations in PKCS#1 v2.2: an opponent cannot distinguish between
+ * the different error conditions.
+ */
+ if( bad != 0 )
return( POLARSSL_ERR_RSA_INVALID_PADDING );
if (ilen - (p - buf) > output_max_len)
@@ -737,10 +757,9 @@
unsigned char *output,
size_t output_max_len)
{
- int ret, correct = 1;
- size_t ilen, pad_count = 0;
- unsigned char *p, *q;
- unsigned char bt;
+ int ret;
+ size_t ilen, pad_count = 0, i;
+ unsigned char *p, bad, pad_done = 0;
unsigned char buf[POLARSSL_MPI_MAX_SIZE];
if( ctx->padding != RSA_PKCS_V15 )
@@ -759,57 +778,46 @@
return( ret );
p = buf;
+ bad = 0;
- if( *p++ != 0 )
- correct = 0;
+ /*
+ * Check and get padding len in "constant-time"
+ */
+ bad |= *p++; /* First byte must be 0 */
- bt = *p++;
- if( ( bt != RSA_CRYPT && mode == RSA_PRIVATE ) ||
- ( bt != RSA_SIGN && mode == RSA_PUBLIC ) )
+ /* This test does not depend on secret data */
+ if( mode == RSA_PRIVATE )
{
- correct = 0;
- }
+ bad |= *p++ ^ RSA_CRYPT;
- if( bt == RSA_CRYPT )
- {
- while( *p != 0 && p < buf + ilen - 1 )
- pad_count += ( *p++ != 0 );
+ /* Get padding len, but always read till end of buffer
+ * (minus one, for the 00 byte) */
+ for( i = 0; i < ilen - 3; i++ )
+ {
+ pad_done |= ( p[i] == 0 );
+ pad_count += ( pad_done == 0 );
+ }
- correct &= ( *p == 0 && p < buf + ilen - 1 );
-
- q = p;
-
- // Also pass over all other bytes to reduce timing differences
- //
- while ( q < buf + ilen - 1 )
- pad_count += ( *q++ != 0 );
-
- // Prevent compiler optimization of pad_count
- //
- correct |= pad_count & 0x100000; /* Always 0 unless 1M bit keys */
- p++;
+ p += pad_count;
+ bad |= *p++; /* Must be zero */
}
else
{
- while( *p == 0xFF && p < buf + ilen - 1 )
- pad_count += ( *p++ == 0xFF );
+ bad |= *p++ ^ RSA_SIGN;
- correct &= ( *p == 0 && p < buf + ilen - 1 );
+ /* Get padding len, but always read till end of buffer
+ * (minus one, for the 00 byte) */
+ for( i = 0; i < ilen - 3; i++ )
+ {
+ pad_done |= ( p[i] == 0xFF );
+ pad_count += ( pad_done == 0 );
+ }
- q = p;
-
- // Also pass over all other bytes to reduce timing differences
- //
- while ( q < buf + ilen - 1 )
- pad_count += ( *q++ != 0 );
-
- // Prevent compiler optimization of pad_count
- //
- correct |= pad_count & 0x100000; /* Always 0 unless 1M bit keys */
- p++;
+ p += pad_count;
+ bad |= *p++; /* Must be zero */
}
- if( correct == 0 )
+ if( bad )
return( POLARSSL_ERR_RSA_INVALID_PADDING );
if (ilen - (p - buf) > output_max_len)