]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: base64 - Add support for base64url encoding.
authorStephan Bosch <stephan.bosch@dovecot.fi>
Wed, 13 Feb 2019 16:55:31 +0000 (17:55 +0100)
committerVille Savolainen <ville.savolainen@dovecot.fi>
Tue, 10 Sep 2019 07:02:24 +0000 (10:02 +0300)
src/lib/base64.c
src/lib/base64.h
src/lib/test-base64.c

index 0b5f004c87e703b2e58688b335154ee56f5fb130..3e7716170ab07cb19125450632fa5de419fb386b 100644 (file)
@@ -186,3 +186,55 @@ struct base64_scheme base64_scheme = {
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        },
 };
+
+/*
+ * "base64url" encoding scheme (RFC 4648, Section 5)
+ */
+
+struct base64_scheme base64url_scheme = {
+       .encmap = {
+               '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', '-', '_',
+       },
+       .decmap = {
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0-7 */
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 8-15 */
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 16-23 */
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 24-31 */
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 32-39 */
+               0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, /* 40-47 */
+               0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, /* 48-55 */
+               0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 56-63 */
+               0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* 64-71 */
+               0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, /* 72-79 */
+               0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, /* 80-87 */
+               0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0x3f, /* 88-95 */
+               0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, /* 96-103 */
+               0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, /* 104-111 */
+               0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, /* 112-119 */
+               0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, /* 120-127 */
+
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 128-255 */
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+       },
+};
index 664e652309d5d0ab8c8ff7d444a18b3fa3922bca..6c4e5a1c7394ffaf87a458d537ad5ccb2d57c3dd 100644 (file)
@@ -99,4 +99,39 @@ static inline bool base64_is_valid_char(char c)
        return base64_scheme_is_valid_char(&base64_scheme, c);
 }
 
+/*
+ * "base64url" encoding scheme (RFC 4648, Section 5)
+ */
+
+extern struct base64_scheme base64url_scheme;
+
+/* Translates binary data into base64url. See base64_scheme_encode(). */
+static inline void
+base64url_encode(const void *src, size_t src_size, buffer_t *dest)
+{
+       base64_scheme_encode(&base64url_scheme, src, src_size, dest);
+}
+
+/* Translates base64url data into binary and appends it to dest buffer. See
+   base64_scheme_decode(). */
+static inline int
+base64url_decode(const void *src, size_t src_size, size_t *src_pos_r,
+                buffer_t *dest) ATTR_NULL(3)
+{
+       return base64_scheme_decode(&base64url_scheme, src, src_size,
+                                   src_pos_r, dest);
+}
+
+/* Decode given string to a buffer allocated from data stack. */
+static inline buffer_t *t_base64url_decode_str(const char *str)
+{
+       return t_base64_scheme_decode_str(&base64url_scheme, str);
+}
+
+/* Returns TRUE if c is a valid base64url encoding character (excluding '=') */
+static inline bool base64url_is_valid_char(char c)
+{
+       return base64_scheme_is_valid_char(&base64url_scheme, c);
+}
+
 #endif
index 3c4abb8926baa6c8c254c95351598b0550ed15a4..69cab35a2a3c12e597836fc75ecbc8f763fbc1b5 100644 (file)
@@ -4,7 +4,6 @@
 #include "str.h"
 #include "base64.h"
 
-
 static void test_base64_encode(void)
 {
        const struct {
@@ -14,6 +13,12 @@ static void test_base64_encode(void)
                { "hello world", "aGVsbG8gd29ybGQ=" },
                { "foo barits", "Zm9vIGJhcml0cw==" },
                { "just niin", "anVzdCBuaWlu" },
+               { "\xe7\x8c\xbf\xe3\x82\x82\xe6\x9c\xa8\xe3\x81\x8b"
+                 "\xe3\x82\x89\xe8\x90\xbd\xe3\x81\xa1\xe3\x82\x8b",
+                 "54y/44KC5pyo44GL44KJ6JC944Gh44KL" },
+               { "\xe8\xa7\x92\xe3\x82\x92\xe7\x9f\xaf\xe3\x82\x81\xe3\x81"
+                 "\xa6\xe7\x89\x9b\xe3\x82\x92\xe6\xae\xba\xe3\x81\x99",
+                 "6KeS44KS55+v44KB44Gm54mb44KS5q6644GZ" },
        };
        string_t *str;
        unsigned int i;
@@ -53,6 +58,12 @@ static void test_base64_decode(void)
                  "hel", -1, 4 },
                { "aGVs!!!!!",
                  "hel", -1, 4 },
+               { "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgt"
+                 "C+INC60YPRgCDQtNC+0Y/MgdGCLg==",
+                 "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc"
+                 "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0"
+                 "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc"
+                 "\x81\xd1\x82\x2e", 0, UINT_MAX },
        };
        string_t *str;
        buffer_t buf;
@@ -118,9 +129,127 @@ static void test_base64_random(void)
        test_end();
 }
 
+static void test_base64url_encode(void)
+{
+       const struct {
+               const char *input;
+               const char *output;
+       } tests[] = {
+               { "hello world", "aGVsbG8gd29ybGQ=" },
+               { "foo barits", "Zm9vIGJhcml0cw==" },
+               { "just niin", "anVzdCBuaWlu" },
+               { "\xe7\x8c\xbf\xe3\x82\x82\xe6\x9c\xa8\xe3\x81\x8b"
+                 "\xe3\x82\x89\xe8\x90\xbd\xe3\x81\xa1\xe3\x82\x8b",
+                 "54y_44KC5pyo44GL44KJ6JC944Gh44KL" },
+               { "\xe8\xa7\x92\xe3\x82\x92\xe7\x9f\xaf\xe3\x82\x81\xe3\x81"
+                 "\xa6\xe7\x89\x9b\xe3\x82\x92\xe6\xae\xba\xe3\x81\x99",
+                 "6KeS44KS55-v44KB44Gm54mb44KS5q6644GZ" },
+       };
+       string_t *str;
+       unsigned int i;
+
+       test_begin("base64url_encode()");
+       str = t_str_new(256);
+       for (i = 0; i < N_ELEMENTS(tests); i++) {
+               str_truncate(str, 0);
+               base64url_encode(tests[i].input, strlen(tests[i].input), str);
+               test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i);
+               test_assert_idx(
+                       str_len(str) == MAX_BASE64_ENCODED_SIZE(
+                               strlen(tests[i].input)), i);
+       }
+       test_end();
+}
+
+struct test_base64url_decode {
+       const char *input;
+       const char *output;
+       int ret;
+       unsigned int src_pos;
+};
+
+static void test_base64url_decode(void)
+{
+       static const struct test_base64url_decode tests[] = {
+               { "\taGVsbG8gd29ybGQ=",
+                 "hello world", 0, UINT_MAX },
+               { "\nZm9v\n \tIGJh  \t\ncml0cw==",
+                 "foo barits", 0, UINT_MAX },
+               { "  anVzdCBuaWlu  \n",
+                 "just niin", 1, UINT_MAX },
+               { "aGVsb",
+                 "hel", 1, 4 },
+               { "aGVsb!!!!!",
+                 "hel", -1, 4 },
+               { "aGVs!!!!!",
+                 "hel", -1, 4 },
+               { "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgt"
+                 "C-INC60YPRgCDQtNC-0Y_MgdGCLg==",
+                 "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc"
+                 "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0"
+                 "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc"
+                 "\x81\xd1\x82\x2e", 0, UINT_MAX },
+       };
+       string_t *str;
+       unsigned int i;
+       size_t src_pos;
+       int ret;
+
+       test_begin("base64url_decode()");
+       str = t_str_new(256);
+       for (i = 0; i < N_ELEMENTS(tests); i++) {
+               str_truncate(str, 0);
+
+               src_pos = 0;
+               ret = base64url_decode(tests[i].input, strlen(tests[i].input),
+                                      &src_pos, str);
+
+               test_assert_idx(tests[i].ret == ret, i);
+               test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i);
+               test_assert_idx(src_pos == tests[i].src_pos ||
+                               (tests[i].src_pos == UINT_MAX &&
+                                src_pos == strlen(tests[i].input)), i);
+               if (ret >= 0) {
+                       test_assert_idx(
+                               str_len(str) <= MAX_BASE64_DECODED_SIZE(
+                                       strlen(tests[i].input)), i);
+               }
+       }
+       test_end();
+}
+
+static void test_base64url_random(void)
+{
+       string_t *str, *dest;
+       char buf[10];
+       unsigned int i, j, max;
+
+       str = t_str_new(256);
+       dest = t_str_new(256);
+
+       test_begin("base64url encode/decode with random input");
+       for (i = 0; i < 1000; i++) {
+               max = i_rand_limit(sizeof(buf));
+               for (j = 0; j < max; j++)
+                       buf[j] = i_rand();
+
+               str_truncate(str, 0);
+               str_truncate(dest, 0);
+               base64url_encode(buf, max, str);
+               test_assert_idx(base64url_decode(str_data(str), str_len(str),
+                                                NULL, dest) >= 0, i);
+               test_assert_idx(str_len(dest) == max &&
+                               memcmp(buf, str_data(dest), max) == 0, i);
+       }
+       test_end();
+}
+
 void test_base64(void)
 {
        test_base64_encode();
        test_base64_decode();
        test_base64_random();
+       test_base64url_encode();
+       test_base64url_decode();
+       test_base64url_random();
 }