Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport 2.16: range-based constant-flow base64 #4819

Merged
merged 15 commits into from
Oct 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ChangeLog.d/base64-ranges.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Changes
* Improve the performance of base64 constant-flow code. The result is still
slower than the original non-constant-flow implementation, but much faster
than the previous constant-flow implementation. Fixes #4814.
3 changes: 3 additions & 0 deletions ChangeLog.d/no-strerror.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Bugfix
* Fix the build of sample programs when neither MBEDTLS_ERROR_C nor
MBEDTLS_ERROR_STRERROR_DUMMY is enabled.
255 changes: 96 additions & 159 deletions library/base64.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,127 +66,38 @@
#endif /* MBEDTLS_PLATFORM_C */
#endif /* MBEDTLS_SELF_TEST */

static const unsigned char base64_enc_map[64] =
{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', '+', '/'
};

static const unsigned char base64_dec_map[128] =
{
127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 62, 127, 127, 127, 63, 52, 53,
54, 55, 56, 57, 58, 59, 60, 61, 127, 127,
127, 64, 127, 127, 127, 0, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 127, 127, 127, 127, 127, 127, 26, 27, 28,
29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 127, 127, 127, 127, 127
};

#define BASE64_SIZE_T_MAX ( (size_t) -1 ) /* SIZE_T_MAX is not standard */

/*
* Constant flow conditional assignment to unsigned char
*/
static void mbedtls_base64_cond_assign_uchar( unsigned char * dest, const unsigned char * const src,
unsigned char condition )
{
/* MSVC has a warning about unary minus on unsigned integer types,
* but this is well-defined and precisely what we want to do here. */
#if defined(_MSC_VER)
#pragma warning( push )
#pragma warning( disable : 4146 )
#endif

/* Generate bitmask from condition, mask will either be 0xFF or 0 */
unsigned char mask = ( condition | -condition );
mask >>= 7;
mask = -mask;

#if defined(_MSC_VER)
#pragma warning( pop )
#endif

*dest = ( ( *src ) & mask ) | ( ( *dest ) & ~mask );
}

/*
* Constant flow conditional assignment to uint_32
*/
static void mbedtls_base64_cond_assign_uint32( uint32_t * dest, const uint32_t src,
uint32_t condition )
{
/* MSVC has a warning about unary minus on unsigned integer types,
* but this is well-defined and precisely what we want to do here. */
#if defined(_MSC_VER)
#pragma warning( push )
#pragma warning( disable : 4146 )
#endif

/* Generate bitmask from condition, mask will either be 0xFFFFFFFF or 0 */
uint32_t mask = ( condition | -condition );
mask >>= 31;
mask = -mask;

#if defined(_MSC_VER)
#pragma warning( pop )
#endif

*dest = ( src & mask ) | ( ( *dest ) & ~mask );
}

/*
* Constant flow check for equality
/* Return 0xff if low <= c <= high, 0 otherwise.
*
* Constant flow with respect to c.
*/
static unsigned char mbedtls_base64_eq( size_t in_a, size_t in_b )
static unsigned char mask_of_range( unsigned char low, unsigned char high,
unsigned char c )
{
size_t difference = in_a ^ in_b;

/* MSVC has a warning about unary minus on unsigned integer types,
* but this is well-defined and precisely what we want to do here. */
#if defined(_MSC_VER)
#pragma warning( push )
#pragma warning( disable : 4146 )
#endif

difference |= -difference;

#if defined(_MSC_VER)
#pragma warning( pop )
#endif

/* cope with the varying size of size_t per platform */
difference >>= ( sizeof( difference ) * 8 - 1 );

return (unsigned char) ( 1 ^ difference );
/* low_mask is: 0 if low <= c, 0x...ff if low > c */
unsigned low_mask = ( (unsigned) c - low ) >> 8;
/* high_mask is: 0 if c <= high, 0x...ff if c > high */
unsigned high_mask = ( (unsigned) high - c ) >> 8;
return( ~( low_mask | high_mask ) & 0xff );
}

/*
* Constant flow lookup into table.
/* Given a value in the range 0..63, return the corresponding Base64 digit.
* The implementation assumes that letters are consecutive (e.g. ASCII
* but not EBCDIC).
*/
static unsigned char mbedtls_base64_table_lookup( const unsigned char * const table,
const size_t table_size, const size_t table_index )
static unsigned char enc_char( unsigned char val )
{
size_t i;
unsigned char result = 0;

for( i = 0; i < table_size; ++i )
{
mbedtls_base64_cond_assign_uchar( &result, &table[i], mbedtls_base64_eq( i, table_index ) );
}

return result;
unsigned char digit = 0;
/* For each range of values, if val is in that range, mask digit with
* the corresponding value. Since val can only be in a single range,
* only at most one masking will change digit. */
digit |= mask_of_range( 0, 25, val ) & ( 'A' + val );
digit |= mask_of_range( 26, 51, val ) & ( 'a' + val - 26 );
digit |= mask_of_range( 52, 61, val ) & ( '0' + val - 52 );
digit |= mask_of_range( 62, 62, val ) & '+';
digit |= mask_of_range( 63, 63, val ) & '/';
return( digit );
}

/*
Expand Down Expand Up @@ -229,33 +140,22 @@ int mbedtls_base64_encode( unsigned char *dst, size_t dlen, size_t *olen,
C2 = *src++;
C3 = *src++;

*p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
( ( C1 >> 2 ) & 0x3F ) );

*p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) & 0x3F ) );

*p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
( ( ( ( C2 & 15 ) << 2 ) + ( C3 >> 6 ) ) & 0x3F ) );

*p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
( C3 & 0x3F ) );
*p++ = enc_char( ( C1 >> 2 ) & 0x3F );
*p++ = enc_char( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) & 0x3F );
*p++ = enc_char( ( ( ( C2 & 15 ) << 2 ) + ( C3 >> 6 ) ) & 0x3F );
*p++ = enc_char( C3 & 0x3F );
}

if( i < slen )
{
C1 = *src++;
C2 = ( ( i + 1 ) < slen ) ? *src++ : 0;

*p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
( ( C1 >> 2 ) & 0x3F ) );

*p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) & 0x3F ) );
*p++ = enc_char( ( C1 >> 2 ) & 0x3F );
*p++ = enc_char( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) & 0x3F );

if( ( i + 1 ) < slen )
*p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
( ( ( C2 & 15 ) << 2 ) & 0x3F ) );
*p++ = enc_char( ( ( C2 & 15 ) << 2 ) & 0x3F );
else *p++ = '=';

*p++ = '=';
Expand All @@ -267,26 +167,57 @@ int mbedtls_base64_encode( unsigned char *dst, size_t dlen, size_t *olen,
return( 0 );
}

/* Given a Base64 digit, return its value.
* If c is not a Base64 digit ('A'..'Z', 'a'..'z', '0'..'9', '+' or '/'),
* return -1.
*
* The implementation assumes that letters are consecutive (e.g. ASCII
* but not EBCDIC).
*
* The implementation is constant-flow (no branch or memory access depending
* on the value of c) unless the compiler inlines and optimizes a specific
* access.
*/
static signed char dec_value( unsigned char c )
{
unsigned char val = 0;
/* For each range of digits, if c is in that range, mask val with
* the corresponding value. Since c can only be in a single range,
* only at most one masking will change val. Set val to one plus
* the desired value so that it stays 0 if c is in none of the ranges. */
val |= mask_of_range( 'A', 'Z', c ) & ( c - 'A' + 0 + 1 );
val |= mask_of_range( 'a', 'z', c ) & ( c - 'a' + 26 + 1 );
val |= mask_of_range( '0', '9', c ) & ( c - '0' + 52 + 1 );
val |= mask_of_range( '+', '+', c ) & ( c - '+' + 62 + 1 );
mpg marked this conversation as resolved.
Show resolved Hide resolved
val |= mask_of_range( '/', '/', c ) & ( c - '/' + 63 + 1 );
/* At this point, val is 0 if c is an invalid digit and v+1 if c is
* a digit with the value v. */
return( val - 1 );
}

/*
* Decode a base64-formatted buffer
*/
int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen,
const unsigned char *src, size_t slen )
{
size_t i, n;
uint32_t j, x;
size_t i; /* index in source */
size_t n; /* number of digits or trailing = in source */
mpg marked this conversation as resolved.
Show resolved Hide resolved
uint32_t x; /* value accumulator */
unsigned accumulated_digits = 0;
unsigned equals = 0;
int spaces_present = 0;
unsigned char *p;
unsigned char dec_map_lookup;

/* First pass: check for validity and get output length */
for( i = n = j = 0; i < slen; i++ )
for( i = n = 0; i < slen; i++ )
{
/* Skip spaces before checking for EOL */
x = 0;
spaces_present = 0;
while( i < slen && src[i] == ' ' )
{
++i;
++x;
spaces_present = 1;
}

/* Spaces at end of buffer are OK */
Expand All @@ -301,20 +232,24 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen,
continue;

/* Space inside a line is an error */
if( x != 0 )
if( spaces_present )
return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );

if( src[i] == '=' && ++j > 2 )
return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );

dec_map_lookup = mbedtls_base64_table_lookup( base64_dec_map, sizeof( base64_dec_map ), src[i] );

if( src[i] > 127 || dec_map_lookup == 127 )
return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );

if( dec_map_lookup < 64 && j != 0 )
if( src[i] > 127 )
return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );

if( src[i] == '=' )
{
if( ++equals > 2 )
return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
}
else
{
if( equals != 0 )
return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
if( dec_value( src[i] ) < 0 )
return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
}
n++;
}

Expand All @@ -329,30 +264,32 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen,
* n = ( ( n * 6 ) + 7 ) >> 3;
*/
n = ( 6 * ( n >> 3 ) ) + ( ( 6 * ( n & 0x7 ) + 7 ) >> 3 );
n -= j;
n -= equals;

if( dst == NULL || dlen < n )
{
*olen = n;
return( MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL );
}

for( j = 3, n = x = 0, p = dst; i > 0; i--, src++ )
{
equals = 0;
for( x = 0, p = dst; i > 0; i--, src++ )
{
if( *src == '\r' || *src == '\n' || *src == ' ' )
continue;

dec_map_lookup = mbedtls_base64_table_lookup( base64_dec_map, sizeof( base64_dec_map ), *src );

mbedtls_base64_cond_assign_uint32( &j, j - 1, mbedtls_base64_eq( dec_map_lookup, 64 ) );
x = ( x << 6 ) | ( dec_map_lookup & 0x3F );
x = x << 6;
if( *src == '=' )
++equals;
else
x |= dec_value( *src );

if( ++n == 4 )
if( ++accumulated_digits == 4 )
{
n = 0;
if( j > 0 ) *p++ = (unsigned char)( x >> 16 );
if( j > 1 ) *p++ = (unsigned char)( x >> 8 );
if( j > 2 ) *p++ = (unsigned char)( x );
accumulated_digits = 0;
*p++ = (unsigned char)( x >> 16 );
if( equals <= 1 ) *p++ = (unsigned char)( x >> 8 );
if( equals <= 0 ) *p++ = (unsigned char)( x );
}
}

Expand Down
1 change: 1 addition & 0 deletions programs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ x509/cert_app
x509/cert_req
x509/crl_app
x509/cert_write
x509/load_roots
x509/req_app

# generated files
Expand Down
6 changes: 5 additions & 1 deletion programs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ APPS = aes/crypt_and_hash$(EXEXT) \
util/pem2der$(EXEXT) util/strerror$(EXEXT) \
x509/cert_app$(EXEXT) x509/crl_app$(EXEXT) \
x509/cert_req$(EXEXT) x509/cert_write$(EXEXT) \
x509/req_app$(EXEXT)
x509/load_roots$(EXEXT) x509/req_app$(EXEXT)

ifdef PTHREAD
APPS += ssl/ssl_pthread_server$(EXEXT)
Expand Down Expand Up @@ -285,6 +285,10 @@ x509/cert_req$(EXEXT): x509/cert_req.c $(DEP)
echo " CC x509/cert_req.c"
$(CC) $(LOCAL_CFLAGS) $(CFLAGS) x509/cert_req.c $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@

x509/load_roots$(EXEXT): x509/load_roots.c $(DEP)
echo " CC x509/load_roots.c"
$(CC) $(LOCAL_CFLAGS) $(CFLAGS) x509/load_roots.c $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@

x509/req_app$(EXEXT): x509/req_app.c $(DEP)
echo " CC x509/req_app.c"
$(CC) $(LOCAL_CFLAGS) $(CFLAGS) x509/req_app.c $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@
Expand Down
Loading