Browse Source

Merge pull request #365 from libtom/pr/base64-decode-less-relaxed

Make base64_decode relaxed mode less relaxed
karel-m 7 years ago
parent
commit
ef1fba20b7
4 changed files with 158 additions and 52 deletions
  1. 20 1
      doc/crypt.tex
  2. 4 0
      src/headers/tomcrypt_misc.h
  3. 63 20
      src/misc/base64/base64_decode.c
  4. 71 31
      tests/base64_test.c

+ 20 - 1
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}

+ 4 - 0
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 ---- */

+ 63 - 20
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 == 253) {
+          if (mode == strict)
+             return CRYPT_INVALID_PACKET;
+          else
+             continue; /* allow to ignore white-spaces (relaxed+insane) */
        }
        if (c == 255) {
-          if (is_strict)
-             return CRYPT_INVALID_PACKET;
+          if (mode == insane)
+             continue; /* allow to ignore invalid garbage (insane) */
           else
-             continue;
+             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

+ 71 - 31
tests/base64_test.c

@@ -9,6 +9,8 @@
 #include  <tomcrypt_test.h>
 
 #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/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", 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/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