From 82c04329bf8c3f9fbf75e4b4e83f29245b6c61e0 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 26 Jul 2021 20:20:54 +0200 Subject: [PATCH 01/21] New sample program to benchmark certificate loading Signed-off-by: Gilles Peskine --- programs/.gitignore | 1 + programs/Makefile | 5 + programs/x509/CMakeLists.txt | 1 + programs/x509/load_roots.c | 207 ++++++++++++++++++++++++++++++ visualc/VS2010/load_roots.vcxproj | 167 ++++++++++++++++++++++++ visualc/VS2010/mbedTLS.sln | 13 ++ 6 files changed, 394 insertions(+) create mode 100644 programs/x509/load_roots.c create mode 100644 visualc/VS2010/load_roots.vcxproj diff --git a/programs/.gitignore b/programs/.gitignore index 9816c341d1b3..353a92d4d671 100644 --- a/programs/.gitignore +++ b/programs/.gitignore @@ -65,6 +65,7 @@ x509/cert_app x509/cert_req x509/cert_write x509/crl_app +x509/load_roots x509/req_app # generated files diff --git a/programs/Makefile b/programs/Makefile index 8cd27ea2a0a0..35ef7adb8cc2 100644 --- a/programs/Makefile +++ b/programs/Makefile @@ -106,6 +106,7 @@ APPS = \ x509/cert_req$(EXEXT) \ x509/cert_write$(EXEXT) \ x509/crl_app$(EXEXT) \ + x509/load_roots$(EXEXT) \ x509/req_app$(EXEXT) \ # End of APPS @@ -357,6 +358,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 $@ diff --git a/programs/x509/CMakeLists.txt b/programs/x509/CMakeLists.txt index 29cbeb800205..5b1c3196618f 100644 --- a/programs/x509/CMakeLists.txt +++ b/programs/x509/CMakeLists.txt @@ -15,6 +15,7 @@ set(executables cert_req cert_write crl_app + load_roots req_app ) diff --git a/programs/x509/load_roots.c b/programs/x509/load_roots.c new file mode 100644 index 000000000000..5baf08b0c3df --- /dev/null +++ b/programs/x509/load_roots.c @@ -0,0 +1,207 @@ +/* + * Root CA reading application + * + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + * + * This file is provided under the Apache License 2.0, or the + * GNU General Public License v2.0 or later. + * + * ********** + * Apache License 2.0: + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ********** + * + * ********** + * GNU General Public License v2.0 or later: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * ********** + */ + +#if !defined(MBEDTLS_CONFIG_FILE) +#include "mbedtls/config.h" +#else +#include MBEDTLS_CONFIG_FILE +#endif + +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#include +#include +#define mbedtls_time time +#define mbedtls_time_t time_t +#define mbedtls_fprintf fprintf +#define mbedtls_printf printf +#define mbedtls_exit exit +#define MBEDTLS_EXIT_SUCCESS EXIT_SUCCESS +#define MBEDTLS_EXIT_FAILURE EXIT_FAILURE +#endif /* MBEDTLS_PLATFORM_C */ + +#if !defined(MBEDTLS_X509_CRT_PARSE_C) || !defined(MBEDTLS_FS_IO) || \ + !defined(MBEDTLS_TIMING_C) +int main( void ) +{ + mbedtls_printf("MBEDTLS_X509_CRT_PARSE_C and/or MBEDTLS_FS_IO and/or " + "MBEDTLS_TIMING_C not defined.\n"); + mbedtls_exit( 0 ); +} +#else + +#include "mbedtls/error.h" +#include "mbedtls/timing.h" +#include "mbedtls/x509_crt.h" + +#include +#include +#include + +#define DFL_ITERATIONS 1 +#define DFL_PRIME_CACHE 1 + +#define USAGE \ + "\n usage: load_roots param=<>... [--] {FILE|DIR}...\n" \ + "\n acceptable parameters:\n" \ + " iterations=%%d Iteration count (not including cache priming); default: 1\n" \ + " prime=%%d Prime the disk read cache? Default: 1 (yes)\n" \ + "\n" + + +/* + * global options + */ +struct options +{ + const char **filenames; /* NULL-terminated list of file names */ + unsigned iterations; /* Number of iterations to time */ + int prime_cache; /* Prime the disk read cache? */ +} opt; + + +int read_certificates( const char *const *filenames ) +{ + mbedtls_x509_crt cas; + int ret = 0; + const char *const *cur; + char error_message[200]; + + mbedtls_x509_crt_init( &cas ); + + for( cur = filenames; *cur != NULL; cur++ ) + { + ret = mbedtls_x509_crt_parse_file( &cas, *cur ); + if( ret != 0 ) + { + mbedtls_strerror( ret, error_message, sizeof( error_message ) ); + printf( "\n%s: -0x%04x (%s)\n", *cur, -ret, error_message ); + goto exit; + } + } + +exit: + mbedtls_x509_crt_free( &cas ); + return( ret == 0 ); +} + +int main( int argc, char *argv[] ) +{ + int exit_code = MBEDTLS_EXIT_FAILURE; + unsigned i, j; + struct mbedtls_timing_hr_time timer; + unsigned long ms; + + if( argc == 0 ) + { + mbedtls_printf( USAGE ); + goto exit; + } + + opt.filenames = NULL; + opt.iterations = DFL_ITERATIONS; + opt.prime_cache = DFL_PRIME_CACHE; + + for( i = 1; i < (unsigned) argc; i++ ) + { + char *p = argv[i]; + char *q = NULL; + + if( strcmp( p, "--" ) == 0 ) + break; + if( ( q = strchr( p, '=' ) ) == NULL ) + break; + *q++ = '\0'; + + for( j = 0; p + j < q; j++ ) + { + if( argv[i][j] >= 'A' && argv[i][j] <= 'Z' ) + argv[i][j] |= 0x20; + } + + if( strcmp( p, "iterations" ) == 0 ) + { + opt.iterations = atoi( q ); + } + else if( strcmp( p, "prime" ) == 0 ) + { + opt.iterations = atoi( q ) != 0; + } + else + mbedtls_printf( "Unknown option: %s\n", p ); + } + + opt.filenames = (const char**) argv + i; + if( *opt.filenames == 0 ) + { + mbedtls_printf( "Missing list of certificate files to parse\n" ); + goto exit; + } + + mbedtls_printf( "Parsing %u certificates", argc - i ); + if( opt.prime_cache ) + { + if( ! read_certificates( opt.filenames ) ) + goto exit; + mbedtls_printf( " " ); + } + + (void) mbedtls_timing_get_timer( &timer, 1 ); + for( i = 1; i <= opt.iterations; i++ ) + { + if( ! read_certificates( opt.filenames ) ) + goto exit; + mbedtls_printf( "." ); + } + ms = mbedtls_timing_get_timer( &timer, 0 ); + mbedtls_printf( "\n%u iterations -> %lu ms\n", opt.iterations, ms ); + exit_code = MBEDTLS_EXIT_SUCCESS; + +exit: + mbedtls_exit( exit_code ); +} +#endif /* necessary configuration */ diff --git a/visualc/VS2010/load_roots.vcxproj b/visualc/VS2010/load_roots.vcxproj new file mode 100644 index 000000000000..1eed65778755 --- /dev/null +++ b/visualc/VS2010/load_roots.vcxproj @@ -0,0 +1,167 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + {46cf2d25-6a36-4189-b59c-e4815388e554} + true + + + + {65EB85E6-C928-689F-8335-126F78025220} + Win32Proj + load_roots + + + + Application + true + Unicode + + + Application + true + Unicode + + + Application + false + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + + + + + + + true + $(Configuration)\$(TargetName)\ + + + true + $(Configuration)\$(TargetName)\ + + + false + $(Configuration)\$(TargetName)\ + + + false + $(Configuration)\$(TargetName)\ + + + + Level3 + Disabled + %(PreprocessorDefinitions) + +../../include;../../3rdparty/everest/include/;../../3rdparty/everest/include/everest;../../3rdparty/everest/include/everest/vs2010;../../3rdparty/everest/include/everest/kremlib;../../tests/include + + + Console + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + Debug + + + false + + + + + Level3 + Disabled + %(PreprocessorDefinitions) + +../../include;../../3rdparty/everest/include/;../../3rdparty/everest/include/everest;../../3rdparty/everest/include/everest/vs2010;../../3rdparty/everest/include/everest/kremlib;../../tests/include + + + Console + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + Debug + + + false + + + + + Level3 + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + +../../include;../../3rdparty/everest/include/;../../3rdparty/everest/include/everest;../../3rdparty/everest/include/everest/vs2010;../../3rdparty/everest/include/everest/kremlib;../../tests/include + + + Console + true + true + true + Release + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + +../../include;../../3rdparty/everest/include/;../../3rdparty/everest/include/everest;../../3rdparty/everest/include/everest/vs2010;../../3rdparty/everest/include/everest/kremlib;../../tests/include + + + Console + true + true + true + Release + %(AdditionalDependencies); + + + + + + diff --git a/visualc/VS2010/mbedTLS.sln b/visualc/VS2010/mbedTLS.sln index d1e884e269d4..3624735119d8 100644 --- a/visualc/VS2010/mbedTLS.sln +++ b/visualc/VS2010/mbedTLS.sln @@ -253,6 +253,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "crl_app", "crl_app.vcxproj" {46CF2D25-6A36-4189-B59C-E4815388E554} = {46CF2D25-6A36-4189-B59C-E4815388E554} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "load_roots", "load_roots.vcxproj", "{65EB85E6-C928-689F-8335-126F78025220}" + ProjectSection(ProjectDependencies) = postProject + {46CF2D25-6A36-4189-B59C-E4815388E554} = {46CF2D25-6A36-4189-B59C-E4815388E554} + EndProjectSection +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "req_app", "req_app.vcxproj", "{486B1375-5CFA-C2D2-DD89-C9F497BADCB3}" ProjectSection(ProjectDependencies) = postProject {46CF2D25-6A36-4189-B59C-E4815388E554} = {46CF2D25-6A36-4189-B59C-E4815388E554} @@ -674,6 +679,14 @@ Global {DB904B85-AD31-B7FB-114F-88760CC485F2}.Release|Win32.Build.0 = Release|Win32 {DB904B85-AD31-B7FB-114F-88760CC485F2}.Release|x64.ActiveCfg = Release|x64 {DB904B85-AD31-B7FB-114F-88760CC485F2}.Release|x64.Build.0 = Release|x64 + {65EB85E6-C928-689F-8335-126F78025220}.Debug|Win32.ActiveCfg = Debug|Win32 + {65EB85E6-C928-689F-8335-126F78025220}.Debug|Win32.Build.0 = Debug|Win32 + {65EB85E6-C928-689F-8335-126F78025220}.Debug|x64.ActiveCfg = Debug|x64 + {65EB85E6-C928-689F-8335-126F78025220}.Debug|x64.Build.0 = Debug|x64 + {65EB85E6-C928-689F-8335-126F78025220}.Release|Win32.ActiveCfg = Release|Win32 + {65EB85E6-C928-689F-8335-126F78025220}.Release|Win32.Build.0 = Release|Win32 + {65EB85E6-C928-689F-8335-126F78025220}.Release|x64.ActiveCfg = Release|x64 + {65EB85E6-C928-689F-8335-126F78025220}.Release|x64.Build.0 = Release|x64 {486B1375-5CFA-C2D2-DD89-C9F497BADCB3}.Debug|Win32.ActiveCfg = Debug|Win32 {486B1375-5CFA-C2D2-DD89-C9F497BADCB3}.Debug|Win32.Build.0 = Debug|Win32 {486B1375-5CFA-C2D2-DD89-C9F497BADCB3}.Debug|x64.ActiveCfg = Debug|x64 From 6b541a026b98060a253be2f697146b8d1432ffa9 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 28 Jul 2021 11:33:04 +0200 Subject: [PATCH 02/21] Base64 decoding: don't use the table for '=' Base64 decoding uses equality comparison tests for characters that don't leak information about the content of the data other than its length, such as whitespace. Do this with '=' as well, since it only reveals information about the length. This way the table lookup can focus on character validity and decoding value. Signed-off-by: Gilles Peskine --- library/base64.c | 64 +++++++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/library/base64.c b/library/base64.c index 9cf5dd41d42c..8b818c86a845 100644 --- a/library/base64.c +++ b/library/base64.c @@ -54,7 +54,7 @@ static const unsigned char base64_dec_map[128] = 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, + 127, 127, 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, @@ -90,31 +90,6 @@ static void mbedtls_base64_cond_assign_uchar( unsigned char * dest, const unsign *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 */ @@ -273,17 +248,22 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen, if( x != 0 ) 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( ++j > 2 ) + return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER ); + } + else + { + if( j != 0 ) + return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER ); + dec_map_lookup = mbedtls_base64_table_lookup( base64_dec_map, sizeof( base64_dec_map ), src[i] ); + if( dec_map_lookup == 127 ) + return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER ); + } n++; } @@ -311,10 +291,16 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen, 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 ); + if( *src == '=' ) + { + --j; + x = x << 6; + } + else + { + dec_map_lookup = mbedtls_base64_table_lookup( base64_dec_map, sizeof( base64_dec_map ), *src ); + x = ( x << 6 ) | ( dec_map_lookup & 0x3F ); + } if( ++n == 4 ) { From c48ab114005dc0612d7bba5e3302db3a41b971a1 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 28 Jul 2021 13:54:02 +0200 Subject: [PATCH 03/21] Base64 decoding: use ranges instead of tables Instead of doing constant-flow table lookup, which requires 128 memory loads for each lookup into a 128-entry table, do a range-based calculation, which requires more CPU instructions per range but there are only 5 ranges. Experimentally, this is ~12x faster on my PC (based on programs/x509/load_roots). The code is slightly smaller, too. Signed-off-by: Gilles Peskine --- library/base64.c | 64 +++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/library/base64.c b/library/base64.c index 8b818c86a845..7d9ddf9cd86f 100644 --- a/library/base64.c +++ b/library/base64.c @@ -46,23 +46,6 @@ static const unsigned char base64_enc_map[64] = '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, 127, 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 */ /* @@ -133,6 +116,18 @@ static unsigned char mbedtls_base64_table_lookup( const unsigned char * const ta return result; } +/* Return 0xff if low <= c <= high, 0 otherwise. + * + * Constant flow with respect to c. + */ +static unsigned char mask_of_range( unsigned char low, unsigned char high, + unsigned char c ) +{ + unsigned low_mask = ( c - low ) >> 8; + unsigned high_mask = ( c - high - 1 ) >> 8; + return( ~low_mask & high_mask & 0xff ); +} + /* * Encode a buffer into base64 format */ @@ -211,6 +206,34 @@ 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 ); + 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 */ @@ -220,7 +243,6 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen, size_t i, n; uint32_t j, x; 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++ ) @@ -260,8 +282,7 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen, { if( j != 0 ) return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER ); - dec_map_lookup = mbedtls_base64_table_lookup( base64_dec_map, sizeof( base64_dec_map ), src[i] ); - if( dec_map_lookup == 127 ) + if( dec_value( src[i] ) < 0 ) return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER ); } n++; @@ -298,8 +319,7 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen, } else { - dec_map_lookup = mbedtls_base64_table_lookup( base64_dec_map, sizeof( base64_dec_map ), *src ); - x = ( x << 6 ) | ( dec_map_lookup & 0x3F ); + x = ( x << 6 ) | ( dec_value( *src ) & 0x3F ); } if( ++n == 4 ) From a97e911e5d86164fcac874e02e83219d2fcd57af Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 28 Jul 2021 14:20:06 +0200 Subject: [PATCH 04/21] Base64 decode: simplify local variables Document what each local variable does when it isn't obvious from the name. Don't reuse a variable for different purposes. This commit has very little impact on the generated code (same code size on a sample Thumb build), although it does fix a theoretical bug that 2^32 spaces inside a line would be ignored instead of treated as an error. Signed-off-by: Gilles Peskine --- library/base64.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/library/base64.c b/library/base64.c index 7d9ddf9cd86f..960f778ca13c 100644 --- a/library/base64.c +++ b/library/base64.c @@ -240,19 +240,22 @@ static signed char dec_value( unsigned char c ) 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 */ + uint32_t x; /* value accumulator */ + unsigned equals = 0; + int spaces_present = 0; unsigned char *p; /* 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 */ @@ -267,7 +270,7 @@ 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] > 127 ) @@ -275,12 +278,12 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen, if( src[i] == '=' ) { - if( ++j > 2 ) + if( ++equals > 2 ) return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER ); } else { - if( j != 0 ) + if( equals != 0 ) return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER ); if( dec_value( src[i] ) < 0 ) return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER ); @@ -299,7 +302,7 @@ 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 ) { @@ -307,27 +310,24 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen, return( MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL ); } - for( j = 3, n = x = 0, p = dst; i > 0; i--, src++ ) - { + equals = 0; + for( n = x = 0, p = dst; i > 0; i--, src++ ) + { if( *src == '\r' || *src == '\n' || *src == ' ' ) continue; + x = x << 6; if( *src == '=' ) - { - --j; - x = x << 6; - } + ++equals; else - { - x = ( x << 6 ) | ( dec_value( *src ) & 0x3F ); - } + x |= dec_value( *src ); if( ++n == 4 ) { n = 0; - if( j > 0 ) *p++ = MBEDTLS_BYTE_2( x ); - if( j > 1 ) *p++ = MBEDTLS_BYTE_1( x ); - if( j > 2 ) *p++ = MBEDTLS_BYTE_0( x ); + *p++ = MBEDTLS_BYTE_2( x ); + if( equals <= 1 ) *p++ = MBEDTLS_BYTE_1( x ); + if( equals <= 0 ) *p++ = MBEDTLS_BYTE_0( x ); } } From d6e3f46daaae078c94d1828ab13f6e45326e560c Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 28 Jul 2021 14:31:39 +0200 Subject: [PATCH 05/21] Base64 encoding: use ranges instead of tables Instead of doing constant-flow table lookup, which requires 64 memory loads for each lookup into a 64-entry table, do a range-based calculation, which requires more CPU instructions per range but there are only 5 ranges. I expect a significant performance gain (although smaller than for decoding since the encoding table is half the size), but I haven't measured. Code size is slightly smaller. Signed-off-by: Gilles Peskine --- library/base64.c | 122 ++++++++++------------------------------------- 1 file changed, 25 insertions(+), 97 deletions(-) diff --git a/library/base64.c b/library/base64.c index 960f778ca13c..832484f98fa9 100644 --- a/library/base64.c +++ b/library/base64.c @@ -35,87 +35,8 @@ #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', '+', '/' -}; - #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 check for equality - */ -static unsigned char mbedtls_base64_eq( size_t in_a, size_t in_b ) -{ - 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 ); -} - -/* - * Constant flow lookup into table. - */ -static unsigned char mbedtls_base64_table_lookup( const unsigned char * const table, - const size_t table_size, const size_t table_index ) -{ - 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; -} - /* Return 0xff if low <= c <= high, 0 otherwise. * * Constant flow with respect to c. @@ -128,6 +49,24 @@ static unsigned char mask_of_range( unsigned char low, unsigned char high, return( ~low_mask & high_mask & 0xff ); } +/* 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 enc_char( unsigned char val ) +{ + 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 ); +} + /* * Encode a buffer into base64 format */ @@ -168,17 +107,10 @@ 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 ) @@ -186,15 +118,11 @@ int mbedtls_base64_encode( unsigned char *dst, size_t dlen, size_t *olen, 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++ = '='; From 327c33fc7fad49b2fc3c60a710e8c141b949ef65 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 28 Jul 2021 14:37:14 +0200 Subject: [PATCH 06/21] Base64 range-based constant-flow code: changelog entry Signed-off-by: Gilles Peskine --- ChangeLog.d/base64-ranges.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 ChangeLog.d/base64-ranges.txt diff --git a/ChangeLog.d/base64-ranges.txt b/ChangeLog.d/base64-ranges.txt new file mode 100644 index 000000000000..8ffba2deecd4 --- /dev/null +++ b/ChangeLog.d/base64-ranges.txt @@ -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 implemenation. Fixes #4814. From 831fd766f3f554d50fc82499b008514b833c008a Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 30 Jul 2021 12:56:21 +0200 Subject: [PATCH 07/21] Base64 decode: simplify local variables (n) n was used for two different purposes. Give it a different name the second time. This does not seem to change the generated code when compiling with optimization for size or performance. Signed-off-by: Gilles Peskine --- library/base64.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/library/base64.c b/library/base64.c index 832484f98fa9..d33429645b58 100644 --- a/library/base64.c +++ b/library/base64.c @@ -171,6 +171,7 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen, size_t i; /* index in source */ size_t n; /* number of digits or trailing = in source */ uint32_t x; /* value accumulator */ + unsigned accumulated_digits = 0; unsigned equals = 0; int spaces_present = 0; unsigned char *p; @@ -239,7 +240,7 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen, } equals = 0; - for( n = x = 0, p = dst; i > 0; i--, src++ ) + for( x = 0, p = dst; i > 0; i--, src++ ) { if( *src == '\r' || *src == '\n' || *src == ' ' ) continue; @@ -250,9 +251,9 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen, else x |= dec_value( *src ); - if( ++n == 4 ) + if( ++accumulated_digits == 4 ) { - n = 0; + accumulated_digits = 0; *p++ = MBEDTLS_BYTE_2( x ); if( equals <= 1 ) *p++ = MBEDTLS_BYTE_1( x ); if( equals <= 0 ) *p++ = MBEDTLS_BYTE_0( x ); From a2dc0cc9ea4062dfde69423ddb009617fc891c95 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 30 Jul 2021 12:57:22 +0200 Subject: [PATCH 08/21] mask_of_range: simplify high comparison To test c <= high, instead of testing the sign of (high + 1) - c, negate the sign of high - c (as we're doing for c - low). This is a little easier to read and shaves 2 instructions off the arm thumb build with arm-none-eabi-gcc 7.3.1. Signed-off-by: Gilles Peskine --- library/base64.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/library/base64.c b/library/base64.c index d33429645b58..6ddd501a4f3b 100644 --- a/library/base64.c +++ b/library/base64.c @@ -44,9 +44,11 @@ static unsigned char mask_of_range( unsigned char low, unsigned char high, unsigned char c ) { - unsigned low_mask = ( c - low ) >> 8; - unsigned high_mask = ( c - high - 1 ) >> 8; - return( ~low_mask & high_mask & 0xff ); + /* 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 high > c */ + unsigned high_mask = ( (unsigned) high - c ) >> 8; + return( ~( low_mask | high_mask ) & 0xff ); } /* Given a value in the range 0..63, return the corresponding Base64 digit. From db92884cd013be8b6b511048ea1fc9e52e2c87f8 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 30 Jul 2021 13:00:10 +0200 Subject: [PATCH 09/21] load_roots: arguments must be files I had originally thought to support directories with mbedtls_x509_crt_parse_path but it would have complicated the code more than I cared for. Remove a remnant of the original project in the documentation. Signed-off-by: Gilles Peskine --- programs/x509/load_roots.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/x509/load_roots.c b/programs/x509/load_roots.c index 5baf08b0c3df..de9914e86299 100644 --- a/programs/x509/load_roots.c +++ b/programs/x509/load_roots.c @@ -86,7 +86,7 @@ int main( void ) #define DFL_PRIME_CACHE 1 #define USAGE \ - "\n usage: load_roots param=<>... [--] {FILE|DIR}...\n" \ + "\n usage: load_roots param=<>... [--] FILE...\n" \ "\n acceptable parameters:\n" \ " iterations=%%d Iteration count (not including cache priming); default: 1\n" \ " prime=%%d Prime the disk read cache? Default: 1 (yes)\n" \ From 373c54e174f8882bfc3fd019b8be639abf433d73 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 30 Jul 2021 13:01:36 +0200 Subject: [PATCH 10/21] load_roots: fix no-argument detection Signed-off-by: Gilles Peskine --- programs/x509/load_roots.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/x509/load_roots.c b/programs/x509/load_roots.c index de9914e86299..69357ff3ad0a 100644 --- a/programs/x509/load_roots.c +++ b/programs/x509/load_roots.c @@ -136,7 +136,7 @@ int main( int argc, char *argv[] ) struct mbedtls_timing_hr_time timer; unsigned long ms; - if( argc == 0 ) + if( argc <= 1 ) { mbedtls_printf( USAGE ); goto exit; From 38d41b98b399f29c8693c41d61f92fdba5d0b58e Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 30 Jul 2021 13:01:52 +0200 Subject: [PATCH 11/21] load_roots: properly error out on an invalid option Signed-off-by: Gilles Peskine --- programs/x509/load_roots.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/programs/x509/load_roots.c b/programs/x509/load_roots.c index 69357ff3ad0a..040ef296e4a0 100644 --- a/programs/x509/load_roots.c +++ b/programs/x509/load_roots.c @@ -172,7 +172,11 @@ int main( int argc, char *argv[] ) opt.iterations = atoi( q ) != 0; } else + { mbedtls_printf( "Unknown option: %s\n", p ); + mbedtls_printf( USAGE ); + goto exit; + } } opt.filenames = (const char**) argv + i; From 2c78f980711481f70478d9e3a3d08ca15c59f52c Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 3 Aug 2021 12:19:30 +0200 Subject: [PATCH 12/21] Expose internal base64 functions for testing Signed-off-by: Gilles Peskine --- include/mbedtls/base64.h | 10 ++++++++ library/base64.c | 53 +++++++++++++++++++++++----------------- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/include/mbedtls/base64.h b/include/mbedtls/base64.h index cf4149e731dc..e86a027729a5 100644 --- a/include/mbedtls/base64.h +++ b/include/mbedtls/base64.h @@ -91,6 +91,16 @@ int mbedtls_base64_self_test( int verbose ); #endif /* MBEDTLS_SELF_TEST */ +#if defined(MBEDTLS_TEST_HOOKS) +/* These functions are only exposed in testing configurations for testing + * purposes and may change or disappear at any time. */ +unsigned char mbedtls_base64_mask_of_range( unsigned char low, + unsigned char high, + unsigned char c ); +unsigned char mbedtls_base64_enc_char( unsigned char val ); +signed char mbedtls_base64_dec_value( unsigned char c ); +#endif + #ifdef __cplusplus } #endif diff --git a/library/base64.c b/library/base64.c index 6ddd501a4f3b..96c94d1c6104 100644 --- a/library/base64.c +++ b/library/base64.c @@ -41,8 +41,10 @@ * * Constant flow with respect to c. */ -static unsigned char mask_of_range( unsigned char low, unsigned char high, - unsigned char c ) +MBEDTLS_STATIC_TESTABLE +unsigned char mbedtls_base64_mask_of_range( unsigned char low, + unsigned char high, + unsigned char c ) { /* low_mask is: 0 if low <= c, 0x...ff if low > c */ unsigned low_mask = ( (unsigned) c - low ) >> 8; @@ -55,17 +57,18 @@ static unsigned char mask_of_range( unsigned char low, unsigned char high, * The implementation assumes that letters are consecutive (e.g. ASCII * but not EBCDIC). */ -static unsigned char enc_char( unsigned char val ) +MBEDTLS_STATIC_TESTABLE +unsigned char mbedtls_base64_enc_char( unsigned char val ) { 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 ) & '/'; + digit |= mbedtls_base64_mask_of_range( 0, 25, val ) & ( 'A' + val ); + digit |= mbedtls_base64_mask_of_range( 26, 51, val ) & ( 'a' + val - 26 ); + digit |= mbedtls_base64_mask_of_range( 52, 61, val ) & ( '0' + val - 52 ); + digit |= mbedtls_base64_mask_of_range( 62, 62, val ) & '+'; + digit |= mbedtls_base64_mask_of_range( 63, 63, val ) & '/'; return( digit ); } @@ -109,10 +112,12 @@ int mbedtls_base64_encode( unsigned char *dst, size_t dlen, size_t *olen, C2 = *src++; C3 = *src++; - *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 ); + *p++ = mbedtls_base64_enc_char( ( C1 >> 2 ) & 0x3F ); + *p++ = mbedtls_base64_enc_char( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) + & 0x3F ); + *p++ = mbedtls_base64_enc_char( ( ( ( C2 & 15 ) << 2 ) + ( C3 >> 6 ) ) + & 0x3F ); + *p++ = mbedtls_base64_enc_char( C3 & 0x3F ); } if( i < slen ) @@ -120,11 +125,12 @@ int mbedtls_base64_encode( unsigned char *dst, size_t dlen, size_t *olen, C1 = *src++; C2 = ( ( i + 1 ) < slen ) ? *src++ : 0; - *p++ = enc_char( ( C1 >> 2 ) & 0x3F ); - *p++ = enc_char( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) & 0x3F ); + *p++ = mbedtls_base64_enc_char( ( C1 >> 2 ) & 0x3F ); + *p++ = mbedtls_base64_enc_char( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) + & 0x3F ); if( ( i + 1 ) < slen ) - *p++ = enc_char( ( ( C2 & 15 ) << 2 ) & 0x3F ); + *p++ = mbedtls_base64_enc_char( ( ( C2 & 15 ) << 2 ) & 0x3F ); else *p++ = '='; *p++ = '='; @@ -147,18 +153,19 @@ int mbedtls_base64_encode( unsigned char *dst, size_t dlen, size_t *olen, * on the value of c) unless the compiler inlines and optimizes a specific * access. */ -static signed char dec_value( unsigned char c ) +MBEDTLS_STATIC_TESTABLE +signed char mbedtls_base64_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 ); - val |= mask_of_range( '/', '/', c ) & ( c - '/' + 63 + 1 ); + val |= mbedtls_base64_mask_of_range( 'A', 'Z', c ) & ( c - 'A' + 0 + 1 ); + val |= mbedtls_base64_mask_of_range( 'a', 'z', c ) & ( c - 'a' + 26 + 1 ); + val |= mbedtls_base64_mask_of_range( '0', '9', c ) & ( c - '0' + 52 + 1 ); + val |= mbedtls_base64_mask_of_range( '+', '+', c ) & ( c - '+' + 62 + 1 ); + val |= mbedtls_base64_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 ); @@ -216,7 +223,7 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen, { if( equals != 0 ) return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER ); - if( dec_value( src[i] ) < 0 ) + if( mbedtls_base64_dec_value( src[i] ) < 0 ) return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER ); } n++; @@ -251,7 +258,7 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen, if( *src == '=' ) ++equals; else - x |= dec_value( *src ); + x |= mbedtls_base64_dec_value( *src ); if( ++accumulated_digits == 4 ) { From 3b1f4c0d7ef00b47e76ed437b4d2043462452466 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 3 Aug 2021 12:38:55 +0200 Subject: [PATCH 13/21] Add unit tests for base64 internal functions Add unit tests for mask_of_range(), enc_char() and dec_value(). When constant-flow testing is enabled, verify that these functions are constant-flow. Signed-off-by: Gilles Peskine --- tests/suites/test_suite_base64.data | 30 +++++++++++++ tests/suites/test_suite_base64.function | 58 +++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/tests/suites/test_suite_base64.data b/tests/suites/test_suite_base64.data index 3a892f4792a0..1f94c54348f0 100644 --- a/tests/suites/test_suite_base64.data +++ b/tests/suites/test_suite_base64.data @@ -1,3 +1,33 @@ +mask_of_range empty (1..0) +mask_of_range:1:0 + +mask_of_range empty (255..0) +mask_of_range:255:0 + +mask_of_range empty (42..7) +mask_of_range:42:7 + +mask_of_range 0..0 +mask_of_range:0:0 + +mask_of_range 42..42 +mask_of_range:42:42 + +mask_of_range 255..255 +mask_of_range:255:255 + +mask_of_range 0..255 +mask_of_range:0:255 + +mask_of_range 'A'..'Z' +mask_of_range:65:90 + +enc_char (all digits) +enc_chars:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + +dec_value (all characters) +dec_chars:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + Test case mbedtls_base64_encode #1 buffer just right mbedtls_base64_encode:"":"":0:0 diff --git a/tests/suites/test_suite_base64.function b/tests/suites/test_suite_base64.function index be9b6e8c3e66..89d7995116ec 100644 --- a/tests/suites/test_suite_base64.function +++ b/tests/suites/test_suite_base64.function @@ -8,6 +8,64 @@ * END_DEPENDENCIES */ +/* BEGIN_CASE depends_on:MBEDTLS_TEST_HOOKS */ +void mask_of_range( int low_arg, int high_arg ) +{ + unsigned char low = low_arg, high = high_arg; + unsigned c; + for( c = 0; c <= 0xff; c++ ) + { + mbedtls_test_set_step( c ); + TEST_CF_SECRET( &c, sizeof( c ) ); + unsigned char m = mbedtls_base64_mask_of_range( low, high, c ); + TEST_CF_PUBLIC( &c, sizeof( c ) ); + if( low <= c && c <= high ) + TEST_EQUAL( m, 0xff ); + else + TEST_EQUAL( m, 0 ); + } +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_TEST_HOOKS */ +void enc_chars( char *chars ) +{ + for( unsigned value = 0; value < 64; value++ ) + { + mbedtls_test_set_step( value ); + TEST_CF_SECRET( &value, sizeof( value ) ); + unsigned char digit = mbedtls_base64_enc_char( value ); + TEST_CF_PUBLIC( &value, sizeof( value ) ); + TEST_CF_PUBLIC( &digit, sizeof( digit ) ); + TEST_EQUAL( digit, chars[value] ); + } +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_TEST_HOOKS */ +void dec_chars( char *chars ) +{ + char *p; + const size_t chars_len = strlen( chars ); + signed char expected; + + for( unsigned c = 0; c <= 0xff; c++ ) + { + mbedtls_test_set_step( c ); + p = memchr( chars, c, chars_len ); + if( p == NULL ) + expected = -1; + else + expected = p - chars; + TEST_CF_SECRET( &c, sizeof( c ) ); + signed char actual = mbedtls_base64_dec_value( c ); + TEST_CF_PUBLIC( &c, sizeof( c ) ); + TEST_CF_PUBLIC( &actual, sizeof( actual ) ); + TEST_EQUAL( actual, expected ); + } +} +/* END_CASE */ + /* BEGIN_CASE */ void mbedtls_base64_encode( char * src_string, char * dst_string, int dst_buf_size, int result ) From 85f3165c6a0bdfb917a99075afb9a1df5a8e9bb9 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 3 Aug 2021 13:15:04 +0200 Subject: [PATCH 14/21] Fix printf format signedness error Signed-off-by: Gilles Peskine --- programs/x509/load_roots.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/programs/x509/load_roots.c b/programs/x509/load_roots.c index 040ef296e4a0..ba398f9b37b8 100644 --- a/programs/x509/load_roots.c +++ b/programs/x509/load_roots.c @@ -119,7 +119,8 @@ int read_certificates( const char *const *filenames ) if( ret != 0 ) { mbedtls_strerror( ret, error_message, sizeof( error_message ) ); - printf( "\n%s: -0x%04x (%s)\n", *cur, -ret, error_message ); + printf( "\n%s: -0x%04x (%s)\n", + *cur, (unsigned) -ret, error_message ); goto exit; } } From 0f4e3fbd10523832ab484018a7c7d4157f5801bc Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 3 Aug 2021 17:41:49 +0200 Subject: [PATCH 15/21] Mark output as public before testing it Signed-off-by: Gilles Peskine --- tests/suites/test_suite_base64.function | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/suites/test_suite_base64.function b/tests/suites/test_suite_base64.function index 89d7995116ec..c0548956ef36 100644 --- a/tests/suites/test_suite_base64.function +++ b/tests/suites/test_suite_base64.function @@ -19,6 +19,7 @@ void mask_of_range( int low_arg, int high_arg ) TEST_CF_SECRET( &c, sizeof( c ) ); unsigned char m = mbedtls_base64_mask_of_range( low, high, c ); TEST_CF_PUBLIC( &c, sizeof( c ) ); + TEST_CF_PUBLIC( &m, sizeof( m ) ); if( low <= c && c <= high ) TEST_EQUAL( m, 0xff ); else From 3f0722c1afab0599151c4687086b917d85985d17 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 6 Aug 2021 14:37:01 +0200 Subject: [PATCH 16/21] Fix the build of sample programs without mbedtls_strerror Signed-off-by: Gilles Peskine --- ChangeLog.d/no-strerror.txt | 3 +++ programs/pkey/key_app_writer.c | 12 +++++++----- programs/x509/load_roots.c | 7 ++++++- 3 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 ChangeLog.d/no-strerror.txt diff --git a/ChangeLog.d/no-strerror.txt b/ChangeLog.d/no-strerror.txt new file mode 100644 index 000000000000..69743a871570 --- /dev/null +++ b/ChangeLog.d/no-strerror.txt @@ -0,0 +1,3 @@ +Bugfix + * Fix the build of sample programs when neither MBEDTLS_ERROR_C nor + MBEDTLS_ERROR_STRERROR_DUMMY is enabled. diff --git a/programs/pkey/key_app_writer.c b/programs/pkey/key_app_writer.c index 6a4d0e4ce630..0d531ea5ad45 100644 --- a/programs/pkey/key_app_writer.c +++ b/programs/pkey/key_app_writer.c @@ -197,7 +197,9 @@ int main( int argc, char *argv[] ) { int ret = 1; int exit_code = MBEDTLS_EXIT_FAILURE; - char buf[1024]; +#if defined(MBEDTLS_ERROR_C) + char buf[200]; +#endif int i; char *p, *q; @@ -208,7 +210,9 @@ int main( int argc, char *argv[] ) * Set to sane values */ mbedtls_pk_init( &key ); +#if defined(MBEDTLS_ERROR_C) memset( buf, 0, sizeof( buf ) ); +#endif mbedtls_mpi_init( &N ); mbedtls_mpi_init( &P ); mbedtls_mpi_init( &Q ); mbedtls_mpi_init( &D ); mbedtls_mpi_init( &E ); mbedtls_mpi_init( &DP ); @@ -296,8 +300,7 @@ int main( int argc, char *argv[] ) if( ret != 0 ) { - mbedtls_strerror( ret, (char *) buf, sizeof(buf) ); - mbedtls_printf( " failed\n ! mbedtls_pk_parse_keyfile returned -0x%04x - %s\n\n", (unsigned int) -ret, buf ); + mbedtls_printf( " failed\n ! mbedtls_pk_parse_keyfile returned -0x%04x", (unsigned int) -ret ); goto exit; } @@ -357,8 +360,7 @@ int main( int argc, char *argv[] ) if( ret != 0 ) { - mbedtls_strerror( ret, (char *) buf, sizeof(buf) ); - mbedtls_printf( " failed\n ! mbedtls_pk_parse_public_key returned -0x%04x - %s\n\n", (unsigned int) -ret, buf ); + mbedtls_printf( " failed\n ! mbedtls_pk_parse_public_key returned -0x%04x", (unsigned int) -ret ); goto exit; } diff --git a/programs/x509/load_roots.c b/programs/x509/load_roots.c index ba398f9b37b8..6a40949ec2dc 100644 --- a/programs/x509/load_roots.c +++ b/programs/x509/load_roots.c @@ -109,7 +109,6 @@ int read_certificates( const char *const *filenames ) mbedtls_x509_crt cas; int ret = 0; const char *const *cur; - char error_message[200]; mbedtls_x509_crt_init( &cas ); @@ -118,9 +117,15 @@ int read_certificates( const char *const *filenames ) ret = mbedtls_x509_crt_parse_file( &cas, *cur ); if( ret != 0 ) { +#if defined(MBEDTLS_ERROR_C) || defined(MBEDTLS_ERROR_STRERROR_DUMMY) + char error_message[200]; mbedtls_strerror( ret, error_message, sizeof( error_message ) ); printf( "\n%s: -0x%04x (%s)\n", *cur, (unsigned) -ret, error_message ); +#else + printf( "\n%s: -0x%04x\n", + *cur, (unsigned) -ret ); +#endif goto exit; } } From a138802230ce91af09596ec6812f985b4b2b21f6 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 6 Aug 2021 14:47:10 +0200 Subject: [PATCH 17/21] Move declarations of testing-only base64 functions to their own header Signed-off-by: Gilles Peskine --- include/mbedtls/base64.h | 10 ----- library/base64.c | 1 + library/base64_invasive.h | 55 +++++++++++++++++++++++++ tests/suites/test_suite_base64.function | 1 + visualc/VS2010/mbedTLS.vcxproj | 1 + 5 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 library/base64_invasive.h diff --git a/include/mbedtls/base64.h b/include/mbedtls/base64.h index e86a027729a5..cf4149e731dc 100644 --- a/include/mbedtls/base64.h +++ b/include/mbedtls/base64.h @@ -91,16 +91,6 @@ int mbedtls_base64_self_test( int verbose ); #endif /* MBEDTLS_SELF_TEST */ -#if defined(MBEDTLS_TEST_HOOKS) -/* These functions are only exposed in testing configurations for testing - * purposes and may change or disappear at any time. */ -unsigned char mbedtls_base64_mask_of_range( unsigned char low, - unsigned char high, - unsigned char c ); -unsigned char mbedtls_base64_enc_char( unsigned char val ); -signed char mbedtls_base64_dec_value( unsigned char c ); -#endif - #ifdef __cplusplus } #endif diff --git a/library/base64.c b/library/base64.c index 96c94d1c6104..085c71f3c1de 100644 --- a/library/base64.c +++ b/library/base64.c @@ -22,6 +22,7 @@ #if defined(MBEDTLS_BASE64_C) #include "mbedtls/base64.h" +#include "base64_invasive.h" #include diff --git a/library/base64_invasive.h b/library/base64_invasive.h new file mode 100644 index 000000000000..9e264719d4f8 --- /dev/null +++ b/library/base64_invasive.h @@ -0,0 +1,55 @@ +/** + * \file base_invasive.h + * + * \brief Base64 module: interfaces for invasive testing only. + * + * The interfaces in this file are intended for testing purposes only. + * They SHOULD NOT be made available in library integrations except when + * building the library for testing. + */ +/* + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MBEDTLS_BASE64_INVASIVE_H +#define MBEDTLS_BASE64_INVASIVE_H + +#include "common.h" + +#if defined(MBEDTLS_TEST_HOOKS) +/* Return 0xff if low <= c <= high, 0 otherwise. + * + * Constant flow with respect to c. + */ +unsigned char mbedtls_base64_mask_of_range( unsigned char low, + unsigned char high, + unsigned char c ); + +/* Given a value in the range 0..63, return the corresponding Base64 digit. + * + * Operates in constant time (no branches or memory access depending on val). + */ +unsigned char mbedtls_base64_enc_char( unsigned char val ); + +/* Given a Base64 digit, return its value. + * If c is not a Base64 digit ('A'..'Z', 'a'..'z', '0'..'9', '+' or '/'), + * return -1. + * + * Operates in constant time (no branches or memory access depending on c). + */ +signed char mbedtls_base64_dec_value( unsigned char c ); +#endif /* MBEDTLS_TEST_HOOKS */ + +#endif /* MBEDTLS_SSL_INVASIVE_H */ diff --git a/tests/suites/test_suite_base64.function b/tests/suites/test_suite_base64.function index c0548956ef36..d0e116770552 100644 --- a/tests/suites/test_suite_base64.function +++ b/tests/suites/test_suite_base64.function @@ -1,5 +1,6 @@ /* BEGIN_HEADER */ #include "mbedtls/base64.h" +#include "base64_invasive.h" #include /* END_HEADER */ diff --git a/visualc/VS2010/mbedTLS.vcxproj b/visualc/VS2010/mbedTLS.vcxproj index cd68bb026dd7..5d5b1d392575 100644 --- a/visualc/VS2010/mbedTLS.vcxproj +++ b/visualc/VS2010/mbedTLS.vcxproj @@ -253,6 +253,7 @@ + From fd489f97c14f36e163ee61671e6f755fe9b6cc19 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 6 Aug 2021 14:55:55 +0200 Subject: [PATCH 18/21] Move the list of Base64 digits out of the test data This is part of the definition of the encoding, not a choice of test parameter, so keep it with the test code. Signed-off-by: Gilles Peskine --- tests/suites/test_suite_base64.data | 4 ++-- tests/suites/test_suite_base64.function | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/tests/suites/test_suite_base64.data b/tests/suites/test_suite_base64.data index 1f94c54348f0..555666807df7 100644 --- a/tests/suites/test_suite_base64.data +++ b/tests/suites/test_suite_base64.data @@ -23,10 +23,10 @@ mask_of_range 'A'..'Z' mask_of_range:65:90 enc_char (all digits) -enc_chars:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" +enc_chars: dec_value (all characters) -dec_chars:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" +dec_chars: Test case mbedtls_base64_encode #1 buffer just right mbedtls_base64_encode:"":"":0:0 diff --git a/tests/suites/test_suite_base64.function b/tests/suites/test_suite_base64.function index d0e116770552..8775c8dfbc70 100644 --- a/tests/suites/test_suite_base64.function +++ b/tests/suites/test_suite_base64.function @@ -2,6 +2,12 @@ #include "mbedtls/base64.h" #include "base64_invasive.h" #include + +#if defined(MBEDTLS_TEST_HOOKS) +static const char digits[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +#endif /* MBEDTLS_TEST_HOOKS */ + /* END_HEADER */ /* BEGIN_DEPENDENCIES @@ -30,7 +36,7 @@ void mask_of_range( int low_arg, int high_arg ) /* END_CASE */ /* BEGIN_CASE depends_on:MBEDTLS_TEST_HOOKS */ -void enc_chars( char *chars ) +void enc_chars( ) { for( unsigned value = 0; value < 64; value++ ) { @@ -39,26 +45,26 @@ void enc_chars( char *chars ) unsigned char digit = mbedtls_base64_enc_char( value ); TEST_CF_PUBLIC( &value, sizeof( value ) ); TEST_CF_PUBLIC( &digit, sizeof( digit ) ); - TEST_EQUAL( digit, chars[value] ); + TEST_EQUAL( digit, digits[value] ); } } /* END_CASE */ /* BEGIN_CASE depends_on:MBEDTLS_TEST_HOOKS */ -void dec_chars( char *chars ) +void dec_chars( ) { char *p; - const size_t chars_len = strlen( chars ); signed char expected; for( unsigned c = 0; c <= 0xff; c++ ) { mbedtls_test_set_step( c ); - p = memchr( chars, c, chars_len ); + /* digits is 0-terminated. sizeof()-1 excludes the trailing 0. */ + p = memchr( digits, c, sizeof( digits ) - 1 ); if( p == NULL ) expected = -1; else - expected = p - chars; + expected = p - digits; TEST_CF_SECRET( &c, sizeof( c ) ); signed char actual = mbedtls_base64_dec_value( c ); TEST_CF_PUBLIC( &c, sizeof( c ) ); From d3e5dd3f3abf11ad78b74f103a049eab553d0744 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 6 Aug 2021 16:54:22 +0200 Subject: [PATCH 19/21] Rename variable to avoid a name clash digits is also a local variable in host_test.function, leading to compilers complaining about that shadowing the global variable in test_suite_base64.function. Signed-off-by: Gilles Peskine --- tests/suites/test_suite_base64.function | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/suites/test_suite_base64.function b/tests/suites/test_suite_base64.function index 8775c8dfbc70..67fbb675059c 100644 --- a/tests/suites/test_suite_base64.function +++ b/tests/suites/test_suite_base64.function @@ -4,7 +4,7 @@ #include #if defined(MBEDTLS_TEST_HOOKS) -static const char digits[] = +static const char base64_digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; #endif /* MBEDTLS_TEST_HOOKS */ @@ -45,7 +45,7 @@ void enc_chars( ) unsigned char digit = mbedtls_base64_enc_char( value ); TEST_CF_PUBLIC( &value, sizeof( value ) ); TEST_CF_PUBLIC( &digit, sizeof( digit ) ); - TEST_EQUAL( digit, digits[value] ); + TEST_EQUAL( digit, base64_digits[value] ); } } /* END_CASE */ @@ -59,12 +59,12 @@ void dec_chars( ) for( unsigned c = 0; c <= 0xff; c++ ) { mbedtls_test_set_step( c ); - /* digits is 0-terminated. sizeof()-1 excludes the trailing 0. */ - p = memchr( digits, c, sizeof( digits ) - 1 ); + /* base64_digits is 0-terminated. sizeof()-1 excludes the trailing 0. */ + p = memchr( base64_digits, c, sizeof( base64_digits ) - 1 ); if( p == NULL ) expected = -1; else - expected = p - digits; + expected = p - base64_digits; TEST_CF_SECRET( &c, sizeof( c ) ); signed char actual = mbedtls_base64_dec_value( c ); TEST_CF_PUBLIC( &c, sizeof( c ) ); From 773f5b7f42c07149c57c463d2b55de0ac74c17a1 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 25 Oct 2021 21:13:27 +0200 Subject: [PATCH 20/21] Fix copypasta in comment Signed-off-by: Gilles Peskine --- library/base64.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/base64.c b/library/base64.c index 085c71f3c1de..a516c1d4a3d5 100644 --- a/library/base64.c +++ b/library/base64.c @@ -49,7 +49,7 @@ unsigned char mbedtls_base64_mask_of_range( unsigned char low, { /* 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 high > c */ + /* 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 ); } From 1222fed513cbeab60fab1b569645541272b41346 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 25 Oct 2021 21:14:06 +0200 Subject: [PATCH 21/21] Fix typo in documentation Signed-off-by: Gilles Peskine --- ChangeLog.d/base64-ranges.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.d/base64-ranges.txt b/ChangeLog.d/base64-ranges.txt index 8ffba2deecd4..e3f3862bfb18 100644 --- a/ChangeLog.d/base64-ranges.txt +++ b/ChangeLog.d/base64-ranges.txt @@ -1,4 +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 implemenation. Fixes #4814. + than the previous constant-flow implementation. Fixes #4814.