From 15bcd35557c426dc1f1ca34bd35dd4ddfb858323 Mon Sep 17 00:00:00 2001 From: Lucas Date: Fri, 12 Feb 2021 02:42:38 +0000 Subject: [PATCH] Add base32 decoder --- Makefile | 2 +- base32.c | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ base32.h | 2 ++ 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 base32.c create mode 100644 base32.h diff --git a/Makefile b/Makefile index 06a6eb3..d3dbef1 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ P = otpcli V = 0.0 -HDR = err.h strtonum.h otp.h +HDR = base32.h err.h strtonum.h otp.h OBJ = cli.o ${HDR:.h=.o} SRC = ${OBJ:.o=.c} diff --git a/base32.c b/base32.c new file mode 100644 index 0000000..a823e4e --- /dev/null +++ b/base32.c @@ -0,0 +1,101 @@ +/* + * otpcli - CLI utility for generating OTPs + * + * Written in 2021 by Lucas + * + * To the extent possible under law, the author(s) have dedicated all + * copyright and related and neighboring rights to this software to the + * public domain worldwide. This software is distributed without any + * warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication + * along with this software. If not, see + * . + */ + +#include +#include +#include + +#include "base32.h" + +#define B32_VALID(a) (('A' <= (a) && (a) <= 'Z') || ('2' <= (a) && (a) <= '7')) +#define B32_DECODE_CHAR(a) ((a) <= '7' ? (a) - '2' + 26 : (a) - 'A') + +static int b32_last_chunk_length[] = { 0, -1, 1, -1, 2, 3, -1, 4 }; + +static void +b32_decode_chunk(unsigned char out[5], const char in[8]) +{ + uint64_t x = 0; + + x |= (uint64_t)B32_DECODE_CHAR(in[0]) << 35; + x |= (uint64_t)B32_DECODE_CHAR(in[1]) << 30; + x |= (uint64_t)B32_DECODE_CHAR(in[2]) << 25; + x |= (uint64_t)B32_DECODE_CHAR(in[3]) << 20; + x |= (uint64_t)B32_DECODE_CHAR(in[4]) << 15; + x |= (uint64_t)B32_DECODE_CHAR(in[5]) << 10; + x |= (uint64_t)B32_DECODE_CHAR(in[6]) << 5; + x |= (uint64_t)B32_DECODE_CHAR(in[7]); + + out[0] = (x >> 32) & 0xff; + out[1] = (x >> 24) & 0xff; + out[2] = (x >> 16) & 0xff; + out[3] = (x >> 8) & 0xff; + out[4] = x & 0xff; +} + +int +b32_decoded_len(const char *s, size_t n) +{ + const char *it, *t; + size_t r, eqc; + + for (it = s; *it != '\0' && B32_VALID(*it); it++) + continue; + if (*it != '\0' && *it != '=') + return -1; + + for (t = it; *t == '=' && t - it < 8; t++) + continue; + eqc = t - it; + if (*t != '\0' || t - it == 8 || b32_last_chunk_length[eqc] == -1) + return -1; + + r = n / 8 * 5 + b32_last_chunk_length[eqc == 0 ? n % 8 : eqc]; + return r > INT_MAX ? -1 : r; +} + +int +b32_decode(unsigned char *out, size_t outlen, const char *in, size_t inlen) +{ + unsigned char tout[5]; + char tin[8]; + size_t i, t, eqc, last_chunk; + + t = inlen; + while (in[inlen - 1] == '=') + inlen--; + eqc = t - inlen; + last_chunk = b32_last_chunk_length[eqc == 0 ? inlen % 8 : eqc]; + + while (inlen >= 8 && outlen >= 5) { + b32_decode_chunk(out, in); + in += 8; + inlen -= 8; + out += 5; + outlen -= 5; + } + + if (inlen > 0) { + if (outlen == 0) + return 0; + + memcpy(tin, in, inlen); + memset(tin + inlen, 'A', sizeof(tin) - inlen); + b32_decode_chunk(tout, tin); + memcpy(out, tout, outlen); + } + + return outlen >= last_chunk; +} diff --git a/base32.h b/base32.h new file mode 100644 index 0000000..965ee41 --- /dev/null +++ b/base32.h @@ -0,0 +1,2 @@ +int b32_decoded_len(const char *, size_t); +int b32_decode(unsigned char *, size_t, const char *, size_t);