From f1c7a9896d510a46248d25651a6fb5ffd87ff644 Mon Sep 17 00:00:00 2001 From: Milan Broz Date: Tue, 13 Jul 2021 22:42:52 +0200 Subject: [PATCH] Add base64 wrappers to crypto_backend. We need LGPL 2.1+ implementation in crypto backend and also this code is much easier to read and maintain. --- lib/crypto_backend/Makemodule.am | 1 + lib/crypto_backend/base64.c | 277 ++++++++++++++++++++++++++++ lib/crypto_backend/crypto_backend.h | 4 + tests/crypto-vectors.c | 86 +++++++++ 4 files changed, 368 insertions(+) create mode 100644 lib/crypto_backend/base64.c diff --git a/lib/crypto_backend/Makemodule.am b/lib/crypto_backend/Makemodule.am index f33cd456..2a893f3b 100644 --- a/lib/crypto_backend/Makemodule.am +++ b/lib/crypto_backend/Makemodule.am @@ -9,6 +9,7 @@ libcrypto_backend_la_SOURCES = \ lib/crypto_backend/crypto_storage.c \ lib/crypto_backend/pbkdf_check.c \ lib/crypto_backend/crc32.c \ + lib/crypto_backend/base64.c \ lib/crypto_backend/argon2_generic.c \ lib/crypto_backend/cipher_generic.c \ lib/crypto_backend/cipher_check.c diff --git a/lib/crypto_backend/base64.c b/lib/crypto_backend/base64.c new file mode 100644 index 00000000..8fdae8bc --- /dev/null +++ b/lib/crypto_backend/base64.c @@ -0,0 +1,277 @@ +/* + * Base64 "Not encryption" helpers, copied and adapted from systemd project. + * + * Copyright (C) 2010 Lennart Poettering + * + * cryptsetup related changes + * Copyright (C) 2021 Milan Broz + * + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This file 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include + +#include "crypto_backend.h" + +#define WHITESPACE " \t\n\r" + +/* https://tools.ietf.org/html/rfc4648#section-4 */ +static char base64char(int x) +{ + static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + return table[x & 63]; +} + +static int unbase64char(char c) +{ + unsigned offset; + + if (c >= 'A' && c <= 'Z') + return c - 'A'; + + offset = 'Z' - 'A' + 1; + + if (c >= 'a' && c <= 'z') + return c - 'a' + offset; + + offset += 'z' - 'a' + 1; + + if (c >= '0' && c <= '9') + return c - '0' + offset; + + offset += '9' - '0' + 1; + + if (c == '+') + return offset; + + offset++; + + if (c == '/') + return offset; + + return -EINVAL; +} + +int crypt_base64_encode(char **out, size_t *out_length, const char *in, size_t in_length) +{ + char *r, *z; + const uint8_t *x; + + assert(in || in_length == 0); + assert(out); + + /* three input bytes makes four output bytes, padding is added so we must round up */ + z = r = malloc(4 * (in_length + 2) / 3 + 1); + if (!r) + return -ENOMEM; + + for (x = (const uint8_t *)in; x < (const uint8_t*)in + (in_length / 3) * 3; x += 3) { + /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */ + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ + *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */ + *(z++) = base64char(x[2] & 63); /* 00ZZZZZZ */ + } + + switch (in_length % 3) { + case 2: + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ + *(z++) = base64char((x[1] & 15) << 2); /* 00YYYY00 */ + *(z++) = '='; + + break; + case 1: + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4); /* 00XX0000 */ + *(z++) = '='; + *(z++) = '='; + + break; + } + + *z = 0; + *out = r; + if (out_length) + *out_length = z - r; + return 0; +} + +static int unbase64_next(const char **p, size_t *l) +{ + int ret; + + assert(p); + assert(l); + + /* Find the next non-whitespace character, and decode it. If we find padding, we return it as INT_MAX. We + * greedily skip all preceding and all following whitespace. */ + + for (;;) { + if (*l == 0) + return -EPIPE; + + if (!strchr(WHITESPACE, **p)) + break; + + /* Skip leading whitespace */ + (*p)++, (*l)--; + } + + if (**p == '=') + ret = INT_MAX; /* return padding as INT_MAX */ + else { + ret = unbase64char(**p); + if (ret < 0) + return ret; + } + + for (;;) { + (*p)++, (*l)--; + + if (*l == 0) + break; + if (!strchr(WHITESPACE, **p)) + break; + + /* Skip following whitespace */ + } + + return ret; +} + +int crypt_base64_decode(char **out, size_t *out_length, const char *in, size_t in_length) +{ + uint8_t *buf = NULL; + const char *x; + uint8_t *z; + size_t len; + int r; + + assert(in || in_length == 0); + assert(out); + assert(out_length); + + if (in_length == (size_t) -1) + in_length = strlen(in); + + /* A group of four input bytes needs three output bytes, in case of padding we need to add two or three extra + * bytes. Note that this calculation is an upper boundary, as we ignore whitespace while decoding */ + len = (in_length / 4) * 3 + (in_length % 4 != 0 ? (in_length % 4) - 1 : 0); + + buf = malloc(len + 1); + if (!buf) + return -ENOMEM; + + for (x = in, z = buf;;) { + int a, b, c, d; /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */ + + a = unbase64_next(&x, &in_length); + if (a == -EPIPE) /* End of string */ + break; + if (a < 0) { + r = a; + goto err; + } + if (a == INT_MAX) { /* Padding is not allowed at the beginning of a 4ch block */ + r = -EINVAL; + goto err; + } + + b = unbase64_next(&x, &in_length); + if (b < 0) { + r = b; + goto err; + } + if (b == INT_MAX) { /* Padding is not allowed at the second character of a 4ch block either */ + r = -EINVAL; + goto err; + } + + c = unbase64_next(&x, &in_length); + if (c < 0) { + r = c; + goto err; + } + + d = unbase64_next(&x, &in_length); + if (d < 0) { + r = d; + goto err; + } + + if (c == INT_MAX) { /* Padding at the third character */ + + if (d != INT_MAX) { /* If the third character is padding, the fourth must be too */ + r = -EINVAL; + goto err; + } + + /* b == 00YY0000 */ + if (b & 15) { + r = -EINVAL; + goto err; + } + + if (in_length > 0) { /* Trailing rubbish? */ + r = -ENAMETOOLONG; + goto err; + } + + *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */ + break; + } + + if (d == INT_MAX) { + /* c == 00ZZZZ00 */ + if (c & 3) { + r = -EINVAL; + goto err; + } + + if (in_length > 0) { /* Trailing rubbish? */ + r = -ENAMETOOLONG; + goto err; + } + + *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ + *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ + break; + } + + *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ + *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ + *(z++) = (uint8_t) c << 6 | (uint8_t) d; /* ZZWWWWWW */ + } + + *z = 0; + + *out_length = (size_t) (z - buf); + *out = (char *)buf; + return 0; +err: + free(buf); + + /* Ignore other errors in crypt_backend */ + if (r < 0 && r != -ENOMEM) + r = -EINVAL; + + return r; +} diff --git a/lib/crypto_backend/crypto_backend.h b/lib/crypto_backend/crypto_backend.h index 88cc2d59..65ac3e8f 100644 --- a/lib/crypto_backend/crypto_backend.h +++ b/lib/crypto_backend/crypto_backend.h @@ -83,6 +83,10 @@ int crypt_pbkdf_perf(const char *kdf, const char *hash, /* CRC32 */ uint32_t crypt_crc32(uint32_t seed, const unsigned char *buf, size_t len); +/* Base64 */ +int crypt_base64_encode(char **out, size_t *out_length, const char *in, size_t in_length); +int crypt_base64_decode(char **out, size_t *out_length, const char *in, size_t in_length); + /* Block ciphers */ int crypt_cipher_ivsize(const char *name, const char *mode); int crypt_cipher_wrapped_key(const char *name, const char *mode); diff --git a/tests/crypto-vectors.c b/tests/crypto-vectors.c index 39cad452..7eb4a121 100644 --- a/tests/crypto-vectors.c +++ b/tests/crypto-vectors.c @@ -953,6 +953,47 @@ static struct cipher_iv_test_vector cipher_iv_test_vectors[] = { }, }}}; +/* Base64 test vectors */ +struct base64_test_vector { + size_t decoded_len; + const char *decoded; + const char *encoded; +}; + +static struct base64_test_vector base64_test_vectors[] = { + { 0, "", "" }, + { 1, "\x00", "AA==" }, + { 1, "f", "Zg==" }, + { 2, "fo", "Zm8=" }, + { 3, "foo", "Zm9v" }, + { 4, "foob", "Zm9vYg==" }, + { 5, "fooba", "Zm9vYmE=" }, + { 6, "foobar", "Zm9vYmFy" }, + { 11, "Hello world", "SGVsbG8gd29ybGQ=" }, + { 22, "\x36\x03\x84\xdc\x4e\x03\x46\xa0\xb5\x2d\x03" + "\x6e\xd0\x56\xed\xa0\x37\x02\xac\xc6\x65\xd1", + "NgOE3E4DRqC1LQNu0FbtoDcCrMZl0Q==" }, + { 3, "***", "Kioq" }, + { 4, "\x01\x02\x03\x04", "AQIDBA==" }, + { 5, "\xAD\xAD\xAD\xAD\xAD", "ra2tra0=" }, + { 5, "\xFF\xFF\xFF\xFF\xFF", "//////8=" }, + { 32, "\x40\xC1\x3F\xBD\x05\x4C\x72\x2A\xA3\xC2\xF2" + "\x11\x73\xC0\x69\xEA\x49\x7D\x35\x29\x6B\xCC" + "\x24\x65\xF6\xF9\xD0\x41\x08\x7B\xD7\xA9", + "QME/vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k=" }, + { 7, "\x54\x0f\xdc\xf0\x0f\xaf\x4a", "VA/c8A+vSg==" }, + {179, "blah blah blah blah blah blah blah blah blah " + "blah blah blah blah blah blah blah blah blah " + "blah blah blah blah blah blah blah blah blah " + "blah blah blah blah blah blah blah blah blah", + "YmxhaCBibGFoIGJsYWggYmxhaCBibGFoIGJsYWggYmxh" + "aCBibGFoIGJsYWggYmxhaCBibGFoIGJsYWggYmxhaCBi" + "bGFoIGJsYWggYmxhaCBibGFoIGJsYWggYmxhaCBibGFo" + "IGJsYWggYmxhaCBibGFoIGJsYWggYmxhaCBibGFoIGJs" + "YWggYmxhaCBibGFoIGJsYWggYmxhaCBibGFoIGJsYWgg" + "YmxhaCBibGFoIGJsYWg=" }, +}; + static int pbkdf_test_vectors(void) { char result[256]; @@ -1325,6 +1366,48 @@ static int check_hash(const char *hash) return EXIT_SUCCESS; } +static int base64_test(void) +{ + int i; + char *s; + size_t s_len; + + for (i = 0; i < ARRAY_SIZE(base64_test_vectors); i++) { + printf("BASE64 %02d ", i); + s = NULL; + s_len = 0; + if (crypt_base64_encode(&s, &s_len, + base64_test_vectors[i].decoded, + base64_test_vectors[i].decoded_len) < 0) { + printf("[ENCODE FAILED]\n"); + return EXIT_FAILURE; + } else if (strcmp(s, base64_test_vectors[i].encoded)) { + printf("[ENCODE FAILED]\n"); + free(s); + return EXIT_FAILURE; + } + printf("[encode]"); + free(s); + + s = NULL; + s_len = 0; + if (crypt_base64_decode(&s, &s_len, + base64_test_vectors[i].encoded, + strlen(base64_test_vectors[i].encoded)) < 0) { + printf("[DECODE FAILED]\n"); + return EXIT_FAILURE; + } else if (s_len != base64_test_vectors[i].decoded_len || + memcmp(s, base64_test_vectors[i].decoded, s_len)) { + printf("[DECODE FAILED]\n"); + return EXIT_FAILURE; + } + printf("[decode]\n"); + free(s); + } + + return EXIT_SUCCESS; +} + static int default_alg_test(void) { printf("Defaults: [LUKS1 hash %s] ", DEFAULT_LUKS1_HASH); @@ -1381,6 +1464,9 @@ int main(__attribute__ ((unused)) int argc, __attribute__ ((unused))char *argv[] if (cipher_iv_test()) exit_test("IV test failed.", EXIT_FAILURE); + if (base64_test()) + exit_test("BASE64 test failed.", EXIT_FAILURE); + if (default_alg_test()) { if (fips_mode()) printf("\nDefault compiled-in algorithms test ignored (FIPS mode on).\n");