diff --git a/doc/crypt.tex b/doc/crypt.tex index feb23990..a32aada3 100644 --- a/doc/crypt.tex +++ b/doc/crypt.tex @@ -6549,7 +6549,8 @@ int base64_decode( const char *in, unsigned long *outlen); \end{verbatim} -The function \textit{base64\_decode} works in a relaxed way which allows decoding some inputs that do not strictly follow the standard. +The function \textit{base64\_decode} works in a dangerously relaxed way which allows decoding some inputs that do not strictly follow the standard. + If you want to be strict during decoding you can use: \index{base64\_strict\_decode()} \begin{verbatim} @@ -6559,6 +6560,16 @@ int base64_strict_decode( const char *in, unsigned long *outlen); \end{verbatim} +There is also so called sane mode that ignores white-spaces (\textit{CR}, \textit{LF}, \textit{TAB}, \textit{space}), +does not care about trailing \textit{=} and also ignores the last input byte in case it is \textit{NUL}. +\index{base64\_sane\_decode()} +\begin{verbatim} +int base64_sane_decode( const char *in, + unsigned long len, + unsigned char *out, + unsigned long *outlen); +\end{verbatim} + \subsection{URL--safe 'base64url' encoding} The characters used in the mappings are: \begin{verbatim} @@ -6567,6 +6578,11 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_ Those characters are sometimes also called URL and filename safe alphabet. The interface is analogous to \textit{base64\_xxxx} functions in previous chapter. +\index{base64url\_encode()} +\index{base64url\_strict\_encode()} +\index{base64url\_decode()} +\index{base64url\_strict\_decode()} +\index{base64url\_sane\_decode()} \begin{verbatim} int base64url_encode(const unsigned char *in, unsigned long len, char *out, unsigned long *outlen); @@ -6579,6 +6595,9 @@ int base64url_decode( const char *in, unsigned long len, int base64url_strict_decode( const char *in, unsigned long len, unsigned char *out, unsigned long *outlen); + +int base64url_sane_decode( const char *in, unsigned long len, + unsigned char *out, unsigned long *outlen); \end{verbatim} \mysection{Base32 Encoding and Decoding} diff --git a/src/headers/tomcrypt_misc.h b/src/headers/tomcrypt_misc.h index b54c52dc..b04fc408 100644 --- a/src/headers/tomcrypt_misc.h +++ b/src/headers/tomcrypt_misc.h @@ -16,6 +16,8 @@ int base64_decode(const char *in, unsigned long len, unsigned char *out, unsigned long *outlen); int base64_strict_decode(const char *in, unsigned long len, unsigned char *out, unsigned long *outlen); +int base64_sane_decode(const char *in, unsigned long inlen, + unsigned char *out, unsigned long *outlen); #endif #ifdef LTC_BASE64_URL @@ -28,6 +30,8 @@ int base64url_decode(const char *in, unsigned long len, unsigned char *out, unsigned long *outlen); int base64url_strict_decode(const char *in, unsigned long len, unsigned char *out, unsigned long *outlen); +int base64url_sane_decode(const char *in, unsigned long inlen, + unsigned char *out, unsigned long *outlen); #endif /* ---- BASE32 Routines ---- */ diff --git a/src/misc/base64/base64_decode.c b/src/misc/base64/base64_decode.c index 50d10747..6af4eb5c 100644 --- a/src/misc/base64/base64_decode.c +++ b/src/misc/base64/base64_decode.c @@ -17,11 +17,16 @@ #if defined(LTC_BASE64) || defined (LTC_BASE64_URL) +/* 253 - ignored in "relaxed" + "insane" mode: TAB(9), CR(13), LF(10), space(32) + * 254 - padding character '=' (allowed only at the end) + * 255 - ignored in "insane" mode, but not allowed in "relaxed" + "strict" mode + */ + #if defined(LTC_BASE64) static const unsigned char map_base64[256] = { -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +255, 255, 255, 255, 255, 255, 255, 255, 255, 253, 253, 255, +255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +255, 255, 255, 255, 255, 255, 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, @@ -45,9 +50,9 @@ static const unsigned char map_base64[256] = { static const unsigned char map_base64url[] = { #if defined(LTC_BASE64_URL) -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +255, 255, 255, 255, 255, 255, 255, 255, 255, 253, 253, 255, +255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +255, 255, 255, 255, 255, 255, 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, @@ -71,13 +76,14 @@ static const unsigned char map_base64url[] = { }; enum { - relaxed = 0, - strict = 1 + insane = 0, + strict = 1, + relaxed = 2 }; static int _base64_decode_internal(const char *in, unsigned long inlen, unsigned char *out, unsigned long *outlen, - const unsigned char *map, int is_strict) + const unsigned char *map, int mode) { unsigned long t, x, y, z; unsigned char c; @@ -89,20 +95,29 @@ static int _base64_decode_internal(const char *in, unsigned long inlen, g = 0; /* '=' counter */ for (x = y = z = t = 0; x < inlen; x++) { + if ((in[x] == 0) && (x == (inlen - 1)) && (mode != strict)) { + continue; /* allow the last byte to be NUL (relaxed+insane) */ + } c = map[(unsigned char)in[x]&0xFF]; if (c == 254) { g++; continue; } - else if (is_strict && g > 0) { - /* we only allow '=' to be at the end */ - return CRYPT_INVALID_PACKET; - } - if (c == 255) { - if (is_strict) + if (c == 253) { + if (mode == strict) return CRYPT_INVALID_PACKET; else - continue; + continue; /* allow to ignore white-spaces (relaxed+insane) */ + } + if (c == 255) { + if (mode == insane) + continue; /* allow to ignore invalid garbage (insane) */ + else + return CRYPT_INVALID_PACKET; + } + if ((g > 0) && (mode != insane)) { + /* we only allow '=' to be at the end (strict+relaxed) */ + return CRYPT_INVALID_PACKET; } t = (t<<6)|c; @@ -118,7 +133,7 @@ static int _base64_decode_internal(const char *in, unsigned long inlen, if (y != 0) { if (y == 1) return CRYPT_INVALID_PACKET; - if ((y + g) != 4 && is_strict && map != map_base64url) return CRYPT_INVALID_PACKET; + if (((y + g) != 4) && (mode == strict) && (map != map_base64url)) return CRYPT_INVALID_PACKET; t = t << (6 * (4 - y)); if (z + y - 1 > *outlen) return CRYPT_BUFFER_OVERFLOW; if (y >= 2) out[z++] = (unsigned char) ((t >> 16) & 255); @@ -130,7 +145,7 @@ static int _base64_decode_internal(const char *in, unsigned long inlen, #if defined(LTC_BASE64) /** - Relaxed base64 decode a block of memory + Dangerously relaxed base64 decode a block of memory @param in The base64 data to decode @param inlen The length of the base64 data @param out [out] The destination of the binary decoded data @@ -140,7 +155,7 @@ static int _base64_decode_internal(const char *in, unsigned long inlen, int base64_decode(const char *in, unsigned long inlen, unsigned char *out, unsigned long *outlen) { - return _base64_decode_internal(in, inlen, out, outlen, map_base64, relaxed); + return _base64_decode_internal(in, inlen, out, outlen, map_base64, insane); } /** @@ -156,11 +171,25 @@ int base64_strict_decode(const char *in, unsigned long inlen, { return _base64_decode_internal(in, inlen, out, outlen, map_base64, strict); } + +/** + Sane base64 decode a block of memory + @param in The base64 data to decode + @param inlen The length of the base64 data + @param out [out] The destination of the binary decoded data + @param outlen [in/out] The max size and resulting size of the decoded data + @return CRYPT_OK if successful +*/ +int base64_sane_decode(const char *in, unsigned long inlen, + unsigned char *out, unsigned long *outlen) +{ + return _base64_decode_internal(in, inlen, out, outlen, map_base64, relaxed); +} #endif /* LTC_BASE64 */ #if defined(LTC_BASE64_URL) /** - Relaxed base64 (URL Safe, RFC 4648 section 5) decode a block of memory + Dangerously relaxed base64 (URL Safe, RFC 4648 section 5) decode a block of memory @param in The base64 data to decode @param inlen The length of the base64 data @param out [out] The destination of the binary decoded data @@ -170,7 +199,7 @@ int base64_strict_decode(const char *in, unsigned long inlen, int base64url_decode(const char *in, unsigned long inlen, unsigned char *out, unsigned long *outlen) { - return _base64_decode_internal(in, inlen, out, outlen, map_base64url, relaxed); + return _base64_decode_internal(in, inlen, out, outlen, map_base64url, insane); } /** @@ -186,6 +215,20 @@ int base64url_strict_decode(const char *in, unsigned long inlen, { return _base64_decode_internal(in, inlen, out, outlen, map_base64url, strict); } + +/** + Sane base64 (URL Safe, RFC 4648 section 5) decode a block of memory + @param in The base64 data to decode + @param inlen The length of the base64 data + @param out [out] The destination of the binary decoded data + @param outlen [in/out] The max size and resulting size of the decoded data + @return CRYPT_OK if successful +*/ +int base64url_sane_decode(const char *in, unsigned long inlen, + unsigned char *out, unsigned long *outlen) +{ + return _base64_decode_internal(in, inlen, out, outlen, map_base64url, relaxed); +} #endif /* LTC_BASE64_URL */ #endif diff --git a/tests/base64_test.c b/tests/base64_test.c index 8ca9861d..7b75ee3e 100644 --- a/tests/base64_test.c +++ b/tests/base64_test.c @@ -9,6 +9,8 @@ #include #if defined(LTC_BASE64) || defined(LTC_BASE64_URL) +enum { insane = 0, strict = 1, relaxed = 2, invalid = 666 }; + int base64_test(void) { unsigned char in[64], tmp[64]; @@ -47,39 +49,68 @@ int base64_test(void) #ifdef LTC_BASE64_URL const struct { const char* s; - int is_strict; + int flag; } url_cases[] = { - {"vuiSPKIl8PiR5O-rC4z9_xTQKZ0", 0}, - {"vuiSPKIl8PiR5O-rC4z9_xTQKZ0=", 1}, - {"vuiS*PKIl8P*iR5O-rC4*z9_xTQKZ0", 0}, - {"vuiS*PKIl8P*iR5O-rC4*z9_xTQKZ0=", 0}, - {"vuiS*PKIl8P*iR5O-rC4*z9_xTQKZ0==", 0}, - {"vuiS*PKIl8P*iR5O-rC4*z9_xTQKZ0===", 0}, - {"vuiS*PKIl8P*iR5O-rC4*z9_xTQKZ0====", 0}, - {"vuiS*=PKIl8P*iR5O-rC4*z9_xTQKZ0=", 0}, - {"vuiS*==PKIl8P*iR5O-rC4*z9_xTQKZ0=", 0}, - {"vuiS*===PKIl8P*iR5O-rC4*z9_xTQKZ0=", 0}, + {"vuiSPKIl8PiR5O-rC4z9_xTQKZ0", strict}, /* 0 */ + {"vuiSPKIl8PiR5O-rC4z9_xTQKZ0=", strict}, + {"vuiS*PKIl8P*iR5O-rC4*z9_xTQKZ0", insane}, + {"vuiS*PKIl8P*iR5O-rC4*z9_xTQKZ0=", insane}, + {"vuiS*PKIl8P*iR5O-rC4*z9_xTQKZ0==", insane}, + {"vuiS*PKIl8P*iR5O-rC4*z9_xTQKZ0===", insane}, /* 5 */ + {"vuiS*PKIl8P*iR5O-rC4*z9_xTQKZ0====", insane}, + {"vuiS*=PKIl8P*iR5O-rC4*z9_xTQKZ0=", insane}, + {"vuiS*==PKIl8P*iR5O-rC4*z9_xTQKZ0=", insane}, + {"vuiS*==\xffPKIl8P*iR5O-rC4*z9_xTQKZ0=", insane}, + {"vuiS PKIl8P\niR5O-rC4\tz9_xTQKZ0", relaxed}, /* 10 */ + {"vuiS PKIl8P\niR5O-rC4\tz9_xTQKZ0=", relaxed}, + {"vuiS PKIl8P\niR5O-rC4\tz9_xTQKZ0==", relaxed}, + {"vuiS PKIl8P\niR5O-rC4\tz9_xTQKZ0===", relaxed}, + {"vuiS PKIl8P\niR5O-rC4\tz9_xTQKZ0====", relaxed}, + {"vuiS\rPKIl8P\niR5O-rC4\tz9_xTQKZ0=", relaxed}, /* 15 */ + {"vuiS\rPKIl8P\niR5O-rC4\tz9_xTQKZ0= = =\x00", relaxed}, + {"\nvuiS\rPKIl8P\niR5O-rC4\tz9_xTQKZ0=\n", relaxed}, + {"vuiSPKIl8PiR5O-rC4z9_xTQK", invalid}, }; for (x = 0; x < sizeof(url_cases)/sizeof(url_cases[0]); ++x) { slen1 = strlen(url_cases[x].s); l1 = sizeof(tmp); - if(url_cases[x].is_strict) + if(url_cases[x].flag == strict) { DO(base64url_strict_decode(url_cases[x].s, slen1, tmp, &l1)); - else + DO(do_compare_testvector(tmp, l1, special_case, sizeof(special_case) - 1, "base64url_strict_decode", x)); + DO(base64url_sane_decode(url_cases[x].s, slen1, tmp, &l1)); + DO(do_compare_testvector(tmp, l1, special_case, sizeof(special_case) - 1, "base64url_sane_decode/strict", x)); DO(base64url_decode(url_cases[x].s, slen1, tmp, &l1)); - DO(do_compare_testvector(tmp, l1, special_case, sizeof(special_case) - 1, "base64url decode", x)); - if(x < 2) { - l2 = sizeof(out); - if(x == 0) - DO(base64url_encode(tmp, l1, out, &l2)); - else - DO(base64url_strict_encode(tmp, l1, out, &l2)); - DO(do_compare_testvector(out, l2, url_cases[x].s, strlen(url_cases[x].s), "base64url encode", x)); + DO(do_compare_testvector(tmp, l1, special_case, sizeof(special_case) - 1, "base64url_decode/strict", x)); + } + else if(url_cases[x].flag == relaxed) { + DO(base64url_strict_decode(url_cases[x].s, slen1, tmp, &l1) == CRYPT_INVALID_PACKET ? CRYPT_OK : CRYPT_FAIL_TESTVECTOR); + DO(base64url_sane_decode(url_cases[x].s, slen1, tmp, &l1)); + DO(do_compare_testvector(tmp, l1, special_case, sizeof(special_case) - 1, "base64url_sane_decode/relaxed", x)); + DO(base64url_decode(url_cases[x].s, slen1, tmp, &l1)); + DO(do_compare_testvector(tmp, l1, special_case, sizeof(special_case) - 1, "base64url_decode/relaxed", x)); + } + else if(url_cases[x].flag == insane) { + DO(base64url_strict_decode(url_cases[x].s, slen1, tmp, &l1) == CRYPT_INVALID_PACKET ? CRYPT_OK : CRYPT_FAIL_TESTVECTOR); + DO(base64url_sane_decode(url_cases[x].s, slen1, tmp, &l1) == CRYPT_INVALID_PACKET ? CRYPT_OK : CRYPT_FAIL_TESTVECTOR); + DO(base64url_decode(url_cases[x].s, slen1, tmp, &l1)); + DO(do_compare_testvector(tmp, l1, special_case, sizeof(special_case) - 1, "base64url_decode/insane", x)); + } + else { /* invalid */ + DO(base64url_strict_decode(url_cases[x].s, slen1, tmp, &l1) == CRYPT_INVALID_PACKET ? CRYPT_OK : CRYPT_FAIL_TESTVECTOR); + DO(base64url_sane_decode(url_cases[x].s, slen1, tmp, &l1) == CRYPT_INVALID_PACKET ? CRYPT_OK : CRYPT_FAIL_TESTVECTOR); + DO(base64url_decode(url_cases[x].s, slen1, tmp, &l1) == CRYPT_INVALID_PACKET ? CRYPT_OK : CRYPT_FAIL_TESTVECTOR); + } + l2 = sizeof(out); + if(x == 0) { + DO(base64url_encode(tmp, l1, out, &l2)); + DO(do_compare_testvector(out, l2, url_cases[x].s, strlen(url_cases[x].s), "base64url_encode", x)); + } + if(x == 1) { + DO(base64url_strict_encode(tmp, l1, out, &l2)); + DO(do_compare_testvector(out, l2, url_cases[x].s, strlen(url_cases[x].s), "base64url_strict_encode", x)); } } - - DO(base64url_strict_decode(url_cases[4].s, slen1, tmp, &l1) == CRYPT_INVALID_PACKET ? CRYPT_OK : CRYPT_INVALID_PACKET); #endif #if defined(LTC_BASE64) @@ -89,10 +120,14 @@ int base64_test(void) slen1 = strlen(cases[x].s); l1 = sizeof(out); DO(base64_encode((unsigned char*)cases[x].s, slen1, out, &l1)); + DO(do_compare_testvector(out, l1, cases[x].b64, strlen(cases[x].b64), "base64_encode", x)); l2 = sizeof(tmp); DO(base64_strict_decode(out, l1, tmp, &l2)); - DO(do_compare_testvector(out, l1, cases[x].b64, strlen(cases[x].b64), "base64 encode", x)); - DO(do_compare_testvector(tmp, l2, cases[x].s, slen1, "base64 decode", x)); + DO(do_compare_testvector(tmp, l2, cases[x].s, slen1, "base64_strict_decode", x)); + DO(base64_sane_decode(out, l1, tmp, &l2)); + DO(do_compare_testvector(tmp, l2, cases[x].s, slen1, "base64_sane_decode", x)); + DO(base64_decode(out, l1, tmp, &l2)); + DO(do_compare_testvector(tmp, l2, cases[x].s, slen1, "base64_decode", x)); } for (x = 0; x < 64; x++) { @@ -106,15 +141,20 @@ int base64_test(void) x--; memmove(&out[11], &out[10], l1 - 10); - out[10] = '='; l1++; l2 = sizeof(tmp); + + out[10] = 0; DO(base64_decode(out, l1, tmp, &l2)); - if (compare_testvector(tmp, l2, in, l2, "relaxed base64 decoding", -1)) { - print_hex("input ", out, l1); - return 1; - } - l2 = sizeof(tmp); + DO(compare_testvector(tmp, l2, in, l2, "insane base64 decoding (NUL)", -1)); + DO(base64_sane_decode(out, l1, tmp, &l2) == CRYPT_INVALID_PACKET ? CRYPT_OK : CRYPT_INVALID_PACKET); + DO(base64_strict_decode(out, l1, tmp, &l2) == CRYPT_INVALID_PACKET ? CRYPT_OK : CRYPT_INVALID_PACKET); + + out[10] = 9; /* tab */ + DO(base64_decode(out, l1, tmp, &l2)); + DO(compare_testvector(tmp, l2, in, l2, "insane base64 decoding (TAB)", -1)); + DO(base64_sane_decode(out, l1, tmp, &l2)); + DO(compare_testvector(tmp, l2, in, l2, "relaxed base64 decoding (TAB)", -1)); DO(base64_strict_decode(out, l1, tmp, &l2) == CRYPT_INVALID_PACKET ? CRYPT_OK : CRYPT_INVALID_PACKET); #endif