diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 28 | ||||
-rw-r--r-- | b32.c | 61 | ||||
-rw-r--r-- | b32.h | 10 | ||||
-rw-r--r-- | cbs.h | 638 | ||||
-rw-r--r-- | main.c | 273 | ||||
-rw-r--r-- | make.c | 226 | ||||
-rw-r--r-- | src/base32.c | 93 | ||||
-rw-r--r-- | src/base32.h | 10 | ||||
-rw-r--r-- | src/common.h | 27 | ||||
-rw-r--r-- | src/hmac.c | 41 | ||||
-rw-r--r-- | src/hmac.h | 11 | ||||
-rw-r--r-- | src/main.c | 191 | ||||
-rw-r--r-- | src/sha1-generic.c | 78 | ||||
-rw-r--r-- | src/sha1-x64.c | 98 | ||||
-rw-r--r-- | src/sha1.c | 76 | ||||
-rw-r--r-- | src/sha1.h | 21 | ||||
-rw-r--r-- | src/xendian.h | 21 |
18 files changed, 1533 insertions, 372 deletions
@@ -1,2 +1,4 @@ totp +totp-x64 +make *.o diff --git a/Makefile b/Makefile deleted file mode 100644 index 03f01c4..0000000 --- a/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -.POSIX: - -CC = cc -CFLAGS = \ - -Wall -Wextra -pedantic -Wshadow -Wpointer-arith -Wcast-align \ - -Wwrite-strings -Wmissing-prototypes -Wmissing-declarations \ - -Wredundant-decls -Wnested-externs -Winline -Wno-long-long \ - -Wconversion -Wstrict-prototypes \ - -O3 -march=native -mtune=native -pipe -LDLIBS = -luriparser -lssl -lcrypto - -PREFIX = /usr/local -DPREFIX = ${DESTDIR}${PREFIX} - -all: totp -totp: main.o b32.o - ${CC} ${LDLIBS} -o $@ main.o b32.o - -main.o: main.c b32.h -b32.o: b32.c b32.h - -install: - mkdir -p ${DPREFIX}/bin ${DPREFIX}/share/man/man1 - cp totp ${DPREFIX}/bin - cp totp.1 ${DPREFIX}/share/man/man1 - -clean: - rm -f totp *.o @@ -1,61 +0,0 @@ -#include <stdbool.h> -#include <stddef.h> -#include <stdint.h> - -#include "b32.h" - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsign-conversion" -static const uint8_t ctov[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, 0, -1, -1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -}; -#pragma GCC diagnostic pop - -bool -b32toa(uint8_t *dst, const char *src, size_t len) -{ - char c; - size_t pad = 0; - uint8_t vs[8]; - - while (src[len - 1 - pad] == '=') { - if (++pad > 6) - return false; - } - - for (size_t i = 0; i < len; i += 8) { - for (size_t j = 0; j < 8; j++) { - c = src[i + j]; - vs[j] = ctov[(uint8_t)c]; - if (vs[j] == (uint8_t)-1) { - if (c == '=' && j >= 8 - pad) - vs[j] = 0; - else - return false; - } - } - - dst[i * 5 / 8 + 0] = (vs[0] << 3) | (vs[1] >> 2); - dst[i * 5 / 8 + 1] = (vs[1] << 6) | (vs[2] << 1) | (vs[3] >> 4); - dst[i * 5 / 8 + 2] = (vs[3] << 4) | (vs[4] >> 1); - dst[i * 5 / 8 + 3] = (vs[4] << 7) | (vs[5] << 2) | (vs[6] >> 3); - dst[i * 5 / 8 + 4] = (vs[6] << 5) | (vs[7] >> 0); - } - - return true; -} @@ -1,10 +0,0 @@ -#ifndef B32_B32_H -#define B32_B32_H - -#include <stdbool.h> -#include <stddef.h> -#include <stdint.h> - -bool b32toa(uint8_t *, const char *, size_t); - -#endif @@ -0,0 +1,638 @@ +/* + * BSD Zero Clause License + * + * Copyright © 2023–2024 Thomas Voss + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef C_BUILD_SYSTEM_H +#define C_BUILD_SYSTEM_H + +#define _GNU_SOURCE +#include <sys/stat.h> +#include <sys/wait.h> + +#include <assert.h> +#include <errno.h> +#include <limits.h> +#ifndef CBS_NO_THREADS +# include <pthread.h> +#endif +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wordexp.h> + +#define _vtoxs(...) ((char *[]){__VA_ARGS__}) + +#define lengthof(xs) (sizeof(xs) / sizeof(*(xs))) + +struct strs { + char **buf; + size_t len, cap; +}; + +enum pkg_config_flags { + PC_CFLAGS = 1 << 0, + PC_LIBS = 1 << 1, + PC_SHARED = 1 << 2, + PC_STATIC = 1 << 3, +}; + +static void cbsinit(int, char **); +static void rebuild(const char *); /* Always call via macro wrapper */ +#define rebuild() rebuild(__FILE__) + +static void strsfree(struct strs *); +static void strszero(struct strs *); +static void strspush(struct strs *, char **, size_t); +static void strspushenv(struct strs *, const char *, char **, size_t); +#define strspushl(xs, ...) \ + strspush((xs), _vtoxs(__VA_ARGS__), lengthof(_vtoxs(__VA_ARGS__))) +#define strspushenvl(xs, ev, ...) \ + strspushenv((xs), (ev), _vtoxs(__VA_ARGS__), lengthof(_vtoxs(__VA_ARGS__))) + +static bool fexists(const char *); +static int fmdcmp(const char *, const char *); +static bool fmdolder(const char *, const char *); +static bool fmdnewer(const char *, const char *); +static bool foutdated(const char *, char **, size_t); +#define foutdatedl(s, ...) \ + foutdated((s), _vtoxs(__VA_ARGS__), lengthof(_vtoxs(__VA_ARGS__))) + +static int cmdexec(struct strs); +static pid_t cmdexec_async(struct strs); +static int cmdexec_read(struct strs, char **, size_t *); +static int cmdwait(pid_t); +static void cmdput(struct strs); +static void cmdfput(FILE *, struct strs); + +static char *swpext(const char *, const char *); +static bool pcquery(struct strs *, const char *, int); +static bool binexists(const char *); +static int nproc(void); + +#ifndef CBS_NO_THREADS +typedef void tjob(void *); +typedef void tjob_free(void *); + +struct _tqueue { + void *arg; + tjob *fn; + tjob_free *free; + struct _tqueue *next; +}; + +typedef struct { + bool stop; + size_t tcnt, left; + pthread_t *thrds; + pthread_cond_t cnd; + pthread_mutex_t mtx; + struct _tqueue *head, *tail; +} tpool; + +static void tpinit(tpool *, size_t); +static void tpfree(tpool *); +static void tpwait(tpool *); +static void tpenq(tpool *, tjob *, void *, tjob_free *); +#endif /* !CBS_NO_THREADS */ + +static int _cbs_argc; +static char **_cbs_argv; + +/* Implementation */ + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-function" +#endif + +#ifdef __APPLE__ +# define st_mtim st_mtimespec +#endif + +void +cbsinit(int argc, char **argv) +{ + _cbs_argc = argc; + _cbs_argv = malloc(sizeof(char *) * (argc + 1)); + assert(_cbs_argv != NULL); + for (int i = 0; i < argc; i++) { + _cbs_argv[i] = strdup(argv[i]); + assert(_cbs_argv[i] != NULL); + } + _cbs_argv[argc] = NULL; + + char *s = strrchr(_cbs_argv[0], '/'); + if (s != NULL) { + s[0] = 0; + assert(chdir(_cbs_argv[0]) != -1); + s[0] = '/'; + } +} + +void +(rebuild)(const char *path) +{ + char *src, *dst; + + if ((src = strrchr(path, '/')) != NULL) + src++; + else + src = (char *)path; + + if ((dst = strrchr(*_cbs_argv, '/')) != NULL) + dst++; + else + dst = *_cbs_argv; + + if (!foutdatedl(dst, src)) + return; + + struct strs xs = {0}; + strspushenvl(&xs, "CC", "cc"); +#ifndef CBS_NO_THREADS + strspushl(&xs, "-lpthread"); +#endif + strspushl(&xs, "-o", dst, src); + cmdput(xs); + assert(cmdexec(xs) == EXIT_SUCCESS); + + execvp(*_cbs_argv, _cbs_argv); + assert(!"failed to execute process"); +} + +void +strsfree(struct strs *xs) +{ + free(xs->buf); + xs->buf = NULL; + xs->len = xs->cap = 0; +} + +void +strszero(struct strs *xs) +{ + xs->len = 0; + if (xs->cap > 0) + xs->buf[0] = NULL; +} + +void +strspush(struct strs *xs, char **ys, size_t n) +{ + if (n == 0) + return; + + if (xs->len + n >= xs->cap) { + xs->cap = (xs->len + n) * 2; + xs->buf = realloc(xs->buf, sizeof(char *) * (xs->cap + 1)); + assert(xs->buf != NULL); + } + memcpy(xs->buf + xs->len, ys, n * sizeof(char *)); + xs->len += n; + + assert(xs->len <= xs->cap); + xs->buf[xs->len] = NULL; +} + +void +strspushenv(struct strs *xs, const char *ev, char **ys, size_t n) +{ + /* NOTE: Do your best to NOT modify any pushed envvar! */ + char *p = getenv(ev); + if (p == NULL || *p == 0) { + strspush(xs, ys, n); + return; + } + + wordexp_t we; + assert(wordexp(p, &we, WRDE_NOCMD) == 0); + + /* TODO: Memory leak! */ + for (size_t i = 0; i < we.we_wordc; i++) { + char *w = strdup(we.we_wordv[i]); + assert(w != NULL); + strspushl(xs, w); + } + + wordfree(&we); +} + +bool +fexists(const char *f) +{ + return !access(f, F_OK); +} + +int +fmdcmp(const char *lhs, const char *rhs) +{ + int errnol, errnor; + struct stat sbl, sbr; + + stat(lhs, &sbl); errnol = errno; errno = 0; + stat(rhs, &sbr); errnor = errno; + + assert(errnol == 0 || errnol == ENOENT); + assert(errnor == 0 || errnor == ENOENT); + + if (errnol == ENOENT && errnor == ENOENT) + return 0; + if (errnol == ENOENT) + return -1; + if (errnor == ENOENT) + return +1; + + return sbl.st_mtim.tv_sec == sbr.st_mtim.tv_sec + ? sbl.st_mtim.tv_nsec - sbr.st_mtim.tv_nsec + : sbl.st_mtim.tv_sec - sbr.st_mtim.tv_sec; +} + +bool +fmdnewer(const char *lhs, const char *rhs) +{ + return fmdcmp(lhs, rhs) > 0; +} + +bool +fmdolder(const char *lhs, const char *rhs) +{ + return fmdcmp(lhs, rhs) < 0; +} + +bool +foutdated(const char *src, char **deps, size_t n) +{ + for (size_t i = 0; i < n; i++) { + if (fmdolder(src, deps[i])) + return true; + } + return false; +} + +int +cmdexec(struct strs xs) +{ + return cmdwait(cmdexec_async(xs)); +} + +pid_t +cmdexec_async(struct strs xs) +{ + pid_t pid = fork(); + assert(pid != -1); + if (pid == 0) { + execvp(xs.buf[0], xs.buf); + assert(!"failed to execute process"); + } + return pid; +} + +int +cmdexec_read(struct strs xs, char **p, size_t *n) +{ + enum { + R, + W, + }; + int fds[2]; + + assert(pipe(fds) != -1); + + pid_t pid = fork(); + assert(pid != -1); + + if (pid == 0) { + close(fds[R]); + close(STDOUT_FILENO); + assert(dup2(fds[W], STDOUT_FILENO) != -1); + execvp(xs.buf[0], xs.buf); + assert(!"failed to execute process"); + } + + close(fds[W]); + + struct stat sb; + assert(fstat(fds[R], &sb) != -1); + + *p = NULL, *n = 0; + char *buf = malloc(sb.st_blksize); + assert(buf != NULL); + + for (;;) { + ssize_t nr; + if ((nr = read(fds[R], buf, sb.st_blksize)) == 0) + break; + assert(nr != -1); + + *p = realloc(*p, *n + nr + 1); + assert(*p != NULL); + + memcpy(*p + *n, buf, nr); + *n += nr; + } + + close(fds[R]); + free(buf); + + return cmdwait(pid); +} + +int +cmdwait(pid_t pid) +{ + int ws; + assert(waitpid(pid, &ws, 0) != -1); + if (WIFEXITED(ws)) + return WEXITSTATUS(ws); + return WIFEXITED(ws) ? WEXITSTATUS(ws) : 256; +} + +/* + * import shlex + * + * s = '#define _SHELL_SAFE "' + * for c in map(chr, range(128)): + * if not shlex._find_unsafe(c): + * s += c + * print(s + '"') + */ +#define _SHELL_SAFE \ + "%+,-./0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" + +void +cmdput(struct strs xs) +{ + cmdfput(stdout, xs); +} + +void +cmdfput(FILE *fp, struct strs xs) +{ + flockfile(fp); + for (size_t i = 0; i < xs.len; i++) { + bool safe = true; + const char *p = xs.buf[i]; + + for (const char *q = p; *q; q++) { + if (!strchr(_SHELL_SAFE, *q)) { + safe = false; + break; + } + } + + if (safe) + fputs(p, fp); + else { + putc('\'', fp); + for (const char *q = p; *q; q++) { + if (*q == '\'') + fputs("'\"'\"'", fp); + else + putc(*q, fp); + } + putc('\'', fp); + } + + putc(i == xs.len - 1 ? '\n' : ' ', fp); + } + funlockfile(fp); +} + +bool +pcquery(struct strs *xs, const char *lib, int flags) +{ + struct strs ys = {0}; + + strspushl(&ys, "pkg-config", "--silence-errors"); + if (flags & PC_CFLAGS) + strspushl(&ys, "--cflags"); + if (flags & PC_LIBS) + strspushl(&ys, "--libs"); + if (flags & PC_SHARED) + strspushl(&ys, "--shared"); + if (flags & PC_STATIC) + strspushl(&ys, "--static"); + strspushl(&ys, (char *)lib); + + char *buf; + size_t bufsz; + int ec = cmdexec_read(ys, &buf, &bufsz); + strsfree(&ys); + if (ec != EXIT_SUCCESS) + return false; + + /* Remove trailing newline */ + buf[bufsz - 1] = 0; + + wordexp_t we; + assert(wordexp(buf, &we, WRDE_NOCMD) == 0); + + char **words = malloc(sizeof(char *) * we.we_wordc); + assert(words != NULL); + + /* TODO: Memory leak! */ + for (size_t i = 0; i < we.we_wordc; i++) + assert((words[i] = strdup(we.we_wordv[i])) != NULL); + + strspush(xs, words, we.we_wordc); + wordfree(&we); + free(words); + free(buf); + return true; +} + +bool +binexists(const char *s) +{ + const char *path = getenv("PATH"); + assert(path != NULL); + + char *p = strdup(path), *it; + assert(p != NULL); + + for (it = strtok(p, ":"); it != NULL; it = strtok(NULL, ":")) { + static char buf[PATH_MAX]; + memset(buf, 0, sizeof(buf)); + snprintf(buf, sizeof(buf), "%s/%s", it, s); + if (fexists(buf)) { + free(p); + return true; + } + } + + free(p); + return false; +} + +int +nproc(void) +{ +#ifdef _SC_NPROCESSORS_ONLN + return (int)sysconf(_SC_NPROCESSORS_ONLN); +#else + errno = 0; + return -1; +#endif +} + +char * +swpext(const char *file, const char *ext) +{ + const char *p = strrchr(file, '.'); + if (p == NULL) { + p = strdup(file); + assert(p != NULL); + return (char *)p; + } + + size_t noextlen = p - file; + char *s = malloc(noextlen + strlen(ext) + 2); + assert(s != NULL); + sprintf(s, "%.*s.%s", (int)noextlen, file, ext); + return s; +} + +#ifndef CBS_NO_THREADS +static struct _tqueue * +_tpdeq(tpool *tp) +{ + struct _tqueue *q = tp->head; + + if (q != NULL) { + tp->head = tp->head->next; + if (!tp->head) + tp->tail = NULL; + } + + return q; +} + +static void * +_tpwork(void *arg) +{ + tpool *tp = arg; + + while (!tp->stop) { + struct _tqueue *q; + + pthread_mutex_lock(&tp->mtx); + while (!tp->stop && !tp->head) + pthread_cond_wait(&tp->cnd, &tp->mtx); + if (tp->stop) { + pthread_mutex_unlock(&tp->mtx); + break; + } + + q = _tpdeq(tp); + pthread_mutex_unlock(&tp->mtx); + + q->fn(q->arg); + if (q->free) + q->free(q->arg); + free(q); + + pthread_mutex_lock(&tp->mtx); + tp->left--; + pthread_cond_broadcast(&tp->cnd); + pthread_mutex_unlock(&tp->mtx); + } + + return NULL; +} + +void +tpinit(tpool *tp, size_t n) +{ + tp->tcnt = n; + tp->stop = false; + tp->left = 0; + tp->head = tp->tail = NULL; + tp->thrds = malloc(sizeof(pthread_t) * n); + assert(tp->thrds != NULL); + pthread_cond_init(&tp->cnd, NULL); + pthread_mutex_init(&tp->mtx, NULL); + for (size_t i = 0; i < n; i++) + assert(pthread_create(tp->thrds + i, NULL, _tpwork, tp) == 0); +} + +void +tpfree(tpool *tp) +{ + tp->stop = true; + + pthread_mutex_lock(&tp->mtx); + pthread_cond_broadcast(&tp->cnd); + pthread_mutex_unlock(&tp->mtx); + + for (size_t i = 0; i < tp->tcnt; i++) + pthread_join(tp->thrds[i], NULL); + + free(tp->thrds); + while (tp->head != NULL) { + struct _tqueue *q = _tpdeq(tp); + if (q->free) + q->free(q->arg); + free(q); + } + + pthread_cond_destroy(&tp->cnd); + pthread_mutex_destroy(&tp->mtx); +} + +void +tpwait(tpool *tp) +{ + pthread_mutex_lock(&tp->mtx); + while (!tp->stop && tp->left) + pthread_cond_wait(&tp->cnd, &tp->mtx); + pthread_mutex_unlock(&tp->mtx); +} + +void +tpenq(tpool *tp, tjob *fn, void *arg, tjob_free *free) +{ + struct _tqueue *q = malloc(sizeof(*q)); + assert(q != NULL); + *q = (struct _tqueue){ + .fn = fn, + .arg = arg, + .free = free, + }; + + pthread_mutex_lock(&tp->mtx); + if (tp->tail) + tp->tail->next = q; + if (!tp->head) + tp->head = q; + tp->tail = q; + tp->left++; + pthread_cond_signal(&tp->cnd); + pthread_mutex_unlock(&tp->mtx); +} +#endif /* !CBS_NO_THREADS */ + +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + +#ifdef __APPLE__ +# undef st_mtim +#endif + +#endif /* !C_BUILD_SYSTEM_H */ @@ -1,273 +0,0 @@ -/* References: https://datatracker.ietf.org/doc/html/rfc4226#section-5 */ - -#include <err.h> -#include <getopt.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <strings.h> -#include <time.h> - -#include <openssl/hmac.h> -#include <uriparser/Uri.h> -#include <uriparser/UriBase.h> - -#include "b32.h" - -#define STREQ(x, y) (strcmp(x, y) == 0) -#define WARNX_AND_RET(...) \ - do { \ - rv = EXIT_FAILURE; \ - warnx(__VA_ARGS__); \ - return false; \ - } while (false) - -#define TOTP_DEFAULT (struct totp_config){ .len = 6, .p = 30 } - -typedef unsigned char uchar; - -struct totp_config { - const char *enc_sec; - long len, p; -}; - -extern char *__progname; - -static int rv; - -static const char *bad_scheme = "Invalid scheme ‘%.*s’; expected ‘otpauth’"; -static const char *bad_param = "Invalid ‘%s’ parameter provided"; -static const char *empty_param = "Empty ‘%s’ parameter provided"; -static const char *usage_s = "Usage: %s [-d digits] [-p period] [-u] [secret ...]\n"; - -static void usage(void); -static void totp_print(struct totp_config, char *, bool); -static bool strtol_safe(long *, const char *); -static bool totp(struct totp_config, uint32_t *); -static uint32_t pow32(uint32_t, uint32_t); -static bool uri_parse(struct totp_config *, const char *); -static bool big_endian(void); - -void -usage(void) -{ - fprintf(stderr, usage_s, __progname); - exit(EXIT_FAILURE); -} - -int -main(int argc, char *argv[]) -{ - int opt; - bool uflag = false; - long n; - char *buf; - size_t bufsiz; - ssize_t nr; - struct totp_config conf = TOTP_DEFAULT; - struct option longopts[] = { - {"digits", required_argument, 0, 'd'}, - {"period", required_argument, 0, 'p'}, - {"uri", no_argument, 0, 'u'}, - { NULL, 0, 0, 0 }, - }; - - while ((opt = getopt_long(argc, argv, "d:p:u", longopts, NULL)) != -1) { - switch (opt) { - case 'd': - case 'p': - if (!strtol_safe(&n, optarg)) - errx(EXIT_FAILURE, bad_param, - opt == 'd' ? "digits" : "period"); - if (opt == 'd') - conf.len = n; - else - conf.p = n; - break; - case 'u': - uflag = true; - break; - default: - usage(); - } - } - - argc -= optind; - argv += optind; - - if (argc == 0) { - buf = NULL; - bufsiz = 0; - - while ((nr = getline(&buf, &bufsiz, stdin)) > 0) { - if (buf[--nr] == '\n') - buf[nr] = '\0'; - totp_print(conf, buf, uflag); - } - free(buf); - } else for (int i = 0; i < argc; i++) - totp_print(conf, argv[i], uflag); - - return rv; -} - -void -totp_print(struct totp_config conf, char *buf, bool uflag) -{ - uint32_t code; - - if (uflag) { - conf = TOTP_DEFAULT; - if (!uri_parse(&conf, buf)) - return; - } else - conf.enc_sec = buf; - if (totp(conf, &code)) - printf("%0*d\n", (int)conf.len, code); - if (uflag) - free((void *)conf.enc_sec); -} - -bool -uri_parse(struct totp_config *conf, const char *uri_raw) -{ - bool reject; - size_t len; - UriUriA uri; - UriQueryListA *qs; - const char *epos; - - if (uriParseSingleUriA(&uri, uri_raw, &epos) != URI_SUCCESS) { - len = (size_t)(epos - uri_raw) + 24 + strlen(__progname); - WARNX_AND_RET("Failed to parse URI ‘%s’\n" - "%*c Error detected here", - uri_raw, (int)len, '^'); - } - - len = (size_t)(uri.scheme.afterLast - uri.scheme.first); - reject = len != strlen("otpauth"); - reject = reject || strncasecmp(uri.scheme.first, "otpauth", len) != 0; - - if (reject) - WARNX_AND_RET(bad_scheme, (int)len, uri.scheme.first); - if (uriDissectQueryMallocA(&qs, NULL, uri.query.first, - uri.query.afterLast) != URI_SUCCESS) - WARNX_AND_RET("Failed to parse query string"); - - for (UriQueryListA *p = qs; p != NULL; p = p->next) { - if (STREQ(p->key, "secret")) { - if (p->value == NULL) - WARNX_AND_RET("Secret key has no value"); - if ((conf->enc_sec = strdup(p->value)) == NULL) - err(EXIT_FAILURE, "strdup"); - } else if (STREQ(p->key, "digits")) { - if (p->value == NULL) - WARNX_AND_RET(empty_param, "digits"); - if (!strtol_safe(&conf->len, p->value)) - WARNX_AND_RET(bad_param, "digits"); - } else if (STREQ(p->key, "period")) { - if (p->value == NULL) - WARNX_AND_RET(empty_param, "period"); - if (!strtol_safe(&conf->p, p->value)) - WARNX_AND_RET(bad_param, "period"); - } - } - - uriFreeQueryListA(qs); - uriFreeUriMembersA(&uri); - - return true; -} - -bool -totp(struct totp_config conf, uint32_t *code) -{ - int off; - bool clean; - uint8_t *key; - char *enc_sec; - uchar *mac; - time_t epoch; - uint8_t buf[sizeof(time_t)]; - uint32_t binc; - size_t keylen, enc_sec_len, old; - - /* conf.enc_sec needs to be ‘=’ padded to a multiple of 8 */ - old = enc_sec_len = strlen(conf.enc_sec); - if (enc_sec_len % 8 == 0) { - enc_sec = (char *)conf.enc_sec; - clean = false; - } else { - enc_sec_len += 8 - enc_sec_len % 8; - if ((enc_sec = malloc(enc_sec_len)) == NULL) - err(EXIT_FAILURE, "malloc"); - memcpy(enc_sec, conf.enc_sec, old); - memset(enc_sec + old, '=', enc_sec_len - old); - clean = true; - } - - keylen = (size_t)((double)old / 1.6); - if ((key = calloc(keylen + 1, sizeof(char))) == NULL) - err(EXIT_FAILURE, "calloc"); - b32toa(key, enc_sec, enc_sec_len); - - if (time(&epoch) == (time_t)-1) { - warn("time"); - return false; - } - - epoch /= conf.p; - - if (big_endian()) - memcpy(buf, &epoch, sizeof(time_t)); - else for (size_t i = sizeof(buf); i --> 0;) - buf[sizeof(buf)-1-i] = (uint8_t)((epoch >> 8 * i) & 0xFF); - - mac = HMAC(EVP_sha1(), key, (int)keylen, buf, sizeof(buf), NULL, NULL); - if (mac == NULL) - WARNX_AND_RET("Failed to compute HMAC SHA-1 hash"); - - /* SHA1 hashes are 20 bytes long */ - off = mac[19] & 0x0F; - binc = (mac[off + 0] & 0x7F) << 24 - | (mac[off + 1] & 0xFF) << 16 - | (mac[off + 2] & 0xFF) << 8 - | (mac[off + 3] & 0xFF) << 0; - *code = binc % pow32(10, (uint32_t)conf.len); - - if (clean) - free(enc_sec); - free(key); - return true; -} - -bool -strtol_safe(long *n, const char *s) -{ - char *e; - *n = strtol(s, &e, 10); - return *n > 0 && *s != '\0' && *e == '\0'; -} - -/* This could overflow if you did some autistic shit */ -uint32_t -pow32(uint32_t x, uint32_t y) -{ - uint32_t n = x; - if (y == 0) - return 1; - while (--y != 0) - x *= n; - return x; -} - -bool -big_endian(void) -{ - unsigned n = 0x01020304; - uchar *ptr = (uchar *)&n; - - return *ptr == 1; -} @@ -0,0 +1,226 @@ +#include <assert.h> +#include <getopt.h> +#include <glob.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define CBS_NO_THREADS +#include "cbs.h" + +static void cc(void *); +static void ld(void); +static int globerr(const char *, int); + +static char *warnings[] = { + "-Wall", + "-Wextra", + "-Wpedantic", + "-Wno-parentheses", +}; + +static char *cflags_all[] = { + "-std=c11", +#if __GLIBC__ + "-D_GNU_SOURCE", +#endif +}; + +static char *cflags_dbg[] = { + "-g3", + "-ggdb3", + "-O0", +}; + +static char *cflags_rls[] = { + "-DNDEBUG=1", + "-flto", + "-fomit-frame-pointer", + "-march=native", + "-mtune=native", + "-O3", +}; + +static const char *argv0; +static bool fflag, Sflag, rflag; +static char *oflag = "totp", *pflag = "generic"; + +static void +usage(void) +{ + fprintf(stderr, + "Usage: %s [-p generic|x64] [-fSr]\n" + " %s clean\n", + argv0, argv0); + exit(EXIT_FAILURE); +} + +int +main(int argc, char **argv) +{ + cbsinit(argc, argv); + rebuild(); + + argv0 = argv[0]; + + int opt; + static const struct option longopts[] = { + {"force", no_argument, 0, 'f'}, + {"no-sanitizer", no_argument, 0, 'S'}, + {"output", required_argument, 0, 'o'}, + {"profile", required_argument, 0, 'p'}, + {"release", no_argument, 0, 'r'}, + {0}, + }; + + while ((opt = getopt_long(argc, argv, "fSo:p:r", longopts, NULL)) != -1) { + switch (opt) { + case 'f': + fflag = true; + break; + case 'r': + rflag = true; + /* fallthrough */ + case 'S': + Sflag = true; + break; + case 'o': + oflag = strdup(optarg); + assert(oflag != NULL); + break; + case 'p': + if (strcmp(optarg, "generic") == 0 || strcmp(optarg, "x64") == 0) { + pflag = strdup(optarg); + assert(pflag != NULL); + } else { + fprintf(stderr, "%s: invalid profile -- '%s'\n", argv0, optarg); + usage(); + } + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc > 1) + usage(); + if (argc == 1) { + if (strcmp(argv[0], "clean") != 0) { + fprintf(stderr, "%s: invalid subcommand -- '%s'\n", argv0, *argv); + usage(); + } + struct strs cmd = {0}; + strspushl(&cmd, "find", ".", + "(", + "-name", "totp", + "-or", "-name", "totp-*", + "-or", "-name", "*.o", + ")", "-delete" + ); + cmdput(cmd); + return cmdexec(cmd); + } + + glob_t g; + assert(glob("src/*.c", 0, globerr, &g) == 0); + + char *ext = malloc(strlen(pflag) + sizeof("-.c")); + assert(ext != NULL); + sprintf(ext, "-%s.c", pflag); + + for (size_t i = 0; i < g.gl_pathc; i++) { + if (strchr(g.gl_pathv[i], '-') != NULL + && strstr(g.gl_pathv[i], ext) == NULL) + { + continue; + } + cc(g.gl_pathv[i]); + } + + globfree(&g); + + ld(); +} + +void +cc(void *arg) +{ + struct strs cmd = {0}; + char *dst = swpext(arg, "o"), *src = arg; + + if (!fflag && fmdnewer(dst, src)) + goto out; + + strspushenvl(&cmd, "CC", "cc"); + strspush(&cmd, cflags_all, lengthof(cflags_all)); + if (rflag) + strspushenv(&cmd, "CFLAGS", cflags_rls, lengthof(cflags_rls)); + else { + strspushenv(&cmd, "CFLAGS", cflags_dbg, lengthof(cflags_dbg)); + if (strstr(arg, "-x64.c") != NULL) + strspushl(&cmd, "-msha", "-mssse3"); + } + if (!Sflag) + strspushl(&cmd, "-fsanitize=address,undefined"); + strspushl(&cmd, "-o", dst, "-c", src); + + cmdput(cmd); + cmdexec(cmd); + strsfree(&cmd); +out: + free(dst); +} + +void +ld(void) +{ + glob_t g; + bool dobuild = fflag; + struct strs cmd = {0}; + + strspushenvl(&cmd, "CC", "cc"); + strspush(&cmd, cflags_all, lengthof(cflags_all)); + if (rflag) + strspushenv(&cmd, "CFLAGS", cflags_rls, lengthof(cflags_rls)); + else + strspushenv(&cmd, "CFLAGS", cflags_dbg, lengthof(cflags_dbg)); + if (!Sflag) + strspushl(&cmd, "-fsanitize=address,undefined"); + strspushl(&cmd, "-o", oflag); + + assert(glob("src/*.o", 0, globerr, &g) == 0); + + char *ext = malloc(strlen(pflag) + sizeof("-.o")); + assert(ext != NULL); + sprintf(ext, "-%s.o", pflag); + + for (size_t i = 0; i < g.gl_pathc; i++) { + if (strchr(g.gl_pathv[i], '-') != NULL + && strstr(g.gl_pathv[i], ext) == NULL) + { + continue; + } + if (fmdolder("totp", g.gl_pathv[i])) + dobuild = true; + strspushl(&cmd, g.gl_pathv[i]); + } + + if (dobuild) { + cmdput(cmd); + cmdexec(cmd); + } + + globfree(&g); + strsfree(&cmd); +} + +int +globerr(const char *s, int e) +{ + fprintf(stderr, "glob: %s: %s\n", s, strerror(e)); + exit(EXIT_FAILURE); +} diff --git a/src/base32.c b/src/base32.c new file mode 100644 index 0000000..82c5b48 --- /dev/null +++ b/src/base32.c @@ -0,0 +1,93 @@ +#include <assert.h> + +#include "base32.h" +#include "common.h" + +static inline bool b32blktoa(uint8_t *restrict, const uint8_t *restrict) + __attribute__((always_inline)); + +static const uint8_t lookup[] = { + /* [00…07] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [08…0F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [10…17] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [18…1F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [20…27] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [28…2F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [30…37] = */ 0xFF, 0xFF, 26, 27, 28, 29, 30, 31, + /* [38…3F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0xFF, 0xFF, + /* [40…47] = */ 0xFF, 0, 1, 2, 3, 4, 5, 6, + /* [48…4F] = */ 7, 8, 9, 10, 11, 12, 13, 14, + /* [50…57] = */ 15, 16, 17, 18, 19, 20, 21, 22, + /* [58…5F] = */ 23, 24, 25, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [60…67] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [68…6F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [70…77] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [78…7F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [80…87] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [88…8F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [90…97] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [98…9F] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [A0…A7] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [A8…AF] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [B0…B7] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [B8…BF] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [C0…C7] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [C8…CF] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [D0…D7] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [D8…DF] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [E0…E7] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [E8…EF] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [F0…F7] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* [F8…FF] = */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; + +bool +b32toa(uint8_t *restrict dst, const char *restrict src, size_t len) +{ + assert(len != 0); + + size_t i, j; + for (i = j = 0; len - i >= 8; i += 8, j += 5) { + uint8_t bits[] = { + lookup[(uint8_t)src[i + 0]], + lookup[(uint8_t)src[i + 1]], + lookup[(uint8_t)src[i + 2]], + lookup[(uint8_t)src[i + 3]], + lookup[(uint8_t)src[i + 4]], + lookup[(uint8_t)src[i + 5]], + lookup[(uint8_t)src[i + 6]], + lookup[(uint8_t)src[i + 7]], + }; + if (!b32blktoa(dst + j, bits)) + return false; + } + + uint8_t bits[8] = {0}; + switch (len - i) { + case 7: bits[6] = lookup[(uint8_t)src[i + 6]]; /* fallthrough */ + case 6: bits[5] = lookup[(uint8_t)src[i + 5]]; /* fallthrough */ + case 5: bits[4] = lookup[(uint8_t)src[i + 4]]; /* fallthrough */ + case 4: bits[3] = lookup[(uint8_t)src[i + 3]]; /* fallthrough */ + case 3: bits[2] = lookup[(uint8_t)src[i + 2]]; /* fallthrough */ + case 2: bits[1] = lookup[(uint8_t)src[i + 1]]; /* fallthrough */ + case 1: bits[0] = lookup[(uint8_t)src[i + 0]]; + return b32blktoa(dst + j, bits); + } + return true; +} + +bool +b32blktoa(uint8_t *restrict dst, const uint8_t *restrict src) +{ + uint8_t or = src[0] | src[1] | src[2] | src[3] + | src[4] | src[5] | src[6] | src[7]; + if (or == 0xFF) + return false; + + dst[0] = src[0]<<3 | src[1]>>2; + dst[1] = src[1]<<6 | src[2]<<1 | src[3]>>4; + dst[2] = src[3]<<4 | src[4]>>1; + dst[3] = src[4]<<7 | src[5]<<2 | src[6]>>3; + dst[4] = src[6]<<5 | src[7]>>0; + return true; +} diff --git a/src/base32.h b/src/base32.h new file mode 100644 index 0000000..2581878 --- /dev/null +++ b/src/base32.h @@ -0,0 +1,10 @@ +#ifndef TOTP_BASE32_H +#define TOTP_BASE32_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +bool b32toa(uint8_t *restrict, const char *restrict, size_t); + +#endif /* !TOTP_BASE32_H */ diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..c1d21c8 --- /dev/null +++ b/src/common.h @@ -0,0 +1,27 @@ +#ifndef TOTP_COMMON_H +#define TOTP_COMMON_H + +#if !__GNUC__ +# define __attribute__(x) +#endif + +/* TODO: Is this endian stuff potentially useful? */ + +/* If C23 or newer include this to get byte-order macros */ +#if __STDC_VERSION__ >= 202311L +# include <stdbit.h> +#endif + +#if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) \ + || (defined(__STDC_ENDIAN_NATIVE__) \ + && __STDC_ENDIAN_NATIVE__ == __STDC_ENDIAN_BIG__) +# define ENDIAN_BIG 1 +#elif (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) \ + || (defined(__STDC_ENDIAN_NATIVE__) \ + && __STDC_ENDIAN_NATIVE__ == __STDC_ENDIAN_LITTLE__) +# define ENDIAN_LITTLE 1 +#else +# define ENDIAN_UNKNOWN 1 +#endif + +#endif /* !TOTP_COMMON_H */ diff --git a/src/hmac.c b/src/hmac.c new file mode 100644 index 0000000..5175ee2 --- /dev/null +++ b/src/hmac.c @@ -0,0 +1,41 @@ +#include <string.h> + +#include "sha1.h" + +#define IPAD (0x36) +#define OPAD (0x5C) + +void +hmac_sha1(uint8_t *restrict out, + const uint8_t *restrict key, size_t keysz, + const uint8_t *restrict msg, size_t msgsz) +{ + uint8_t keyext[SHA1BLKSZ] = {0}, + keyipad[SHA1BLKSZ], + keyopad[SHA1BLKSZ]; + + if (keysz > SHA1BLKSZ) { + sha1_t sha; + sha1init(&sha); + sha1hash(&sha, key, keysz); + sha1end(&sha, keyext); + } else + memcpy(keyext, key, keysz); + + for (size_t i = 0; i < sizeof(keyext); i++) { + keyipad[i] = keyext[i] ^ IPAD; + keyopad[i] = keyext[i] ^ OPAD; + } + + sha1_t sha; + uint8_t dgst[SHA1DGSTSZ]; + sha1init(&sha); + sha1hash(&sha, keyipad, sizeof(keyipad)); + sha1hash(&sha, msg, msgsz); + sha1end(&sha, dgst); + + sha1init(&sha); + sha1hash(&sha, keyopad, sizeof(keyopad)); + sha1hash(&sha, dgst, sizeof(dgst)); + sha1end(&sha, out); +} diff --git a/src/hmac.h b/src/hmac.h new file mode 100644 index 0000000..3c3e8e7 --- /dev/null +++ b/src/hmac.h @@ -0,0 +1,11 @@ +#ifndef TOTP_HMAC_H +#define TOTP_HMAC_H + +#include <stddef.h> +#include <stdint.h> + +void hmac_sha1(uint8_t *restrict, + const uint8_t *restrict, size_t, + const uint8_t *restrict, size_t); + +#endif /* !TOTP_HMAC_H */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..40318cf --- /dev/null +++ b/src/main.c @@ -0,0 +1,191 @@ +#include <err.h> +#include <errno.h> +#include <getopt.h> +#include <inttypes.h> +#include <libgen.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdnoreturn.h> +#include <string.h> +#include <time.h> + +#include "base32.h" +#include "common.h" +#include "hmac.h" +#include "sha1.h" +#include "xendian.h" + +static void process(const char *, size_t); +static void process_stdin(void); +static inline uint32_t pow32(uint32_t, uint32_t) + __attribute__((always_inline, const)); +static inline bool xisdigit(char) + __attribute__((always_inline, const)); +static inline bool bigendian(void) + __attribute__((always_inline, const)); + +static int digits = 6, period = 30; + +static noreturn void +usage(const char *argv0) +{ + fprintf(stderr, + "Usage: %s [-d digits] [-p period] [secret]\n" + " %s -h\n", + argv0, argv0); + exit(EXIT_FAILURE); +} + +int +main(int argc, char **argv) +{ + int opt; + static const struct option longopts[] = { + {"digits", required_argument, 0, 'd'}, + {"period", required_argument, 0, 'p'}, + {0}, + }; + + argv[0] = basename(argv[0]); + while ((opt = getopt_long(argc, argv, "d:p:", longopts, NULL)) != -1) { + switch (opt) { + case 'd': + case 'p': { + /* strtol() allows for numbers with leading spaces and a + ‘+’/‘-’. We don’t want that, so assert that the input + begins with a number. */ + if (!xisdigit(optarg[0])) + errx(1, "%s: Invalid integer", optarg); + + errno = 0; + char *endptr; + long n = strtol(optarg, &endptr, 10); + + /* There are trailing invalid digits */ + if (*endptr != 0) + errx(1, "%s: Invalid integer", optarg); + + /* The number was too large. We asserted that the input + didn’t start with ‘-’ so we can ignore checking for + LONG_MIN. */ + if (n > INT_MAX) + errno = ERANGE; + if (errno == ERANGE) + err(1, "%s", optarg); + + if (n == 0) + errx(1, "%s: Integer must be non-zero", optarg); + if (opt == 'd') + digits = (int)n; + else + period = (int)n; + break; + } + default: + usage(argv[0]); + } + } + + argc -= optind; + argv += optind; + + switch (argc) { + case 0: + process_stdin(); + break; + case 1: + process(argv[0], strlen(argv[0])); + break; + default: + usage(argv[-optind]); + } + + return EXIT_SUCCESS; +} + +void +process_stdin(void) +{ + ssize_t nr; + size_t len; + char *line = NULL; + while ((nr = getline(&line, &len, stdin)) != -1) { + if (line[nr - 1] == '\n') + line[--nr] = 0; + process(line, nr); + } + if (errno != 0) + err(1, "getline"); +} + +void +process(const char *s, size_t n) +{ + /* Remove padding bytes */ + while (n > 0 && s[n - 1] == '=') + n--; + if (n == 0) + errx(1, "Empty Base32 input"); + + static uint8_t _key[256]; + uint8_t *key = _key; + + size_t keysz = n * 5 / 8; + if (keysz > sizeof(_key)) { + if ((key = malloc(keysz)) == NULL) + err(1, "malloc"); + } + + if (!b32toa(key, s, n)) + errx(1, "%s: Invalid Base32 input", s); + + /* time(2) claims that this call will never fail if passed a NULL + argument. We cast the time_t to uint64_t which will always be + safe to do. */ + uint64_t epoch = htobe64((uint64_t)time(NULL) / (uint64_t)period); + uint8_t dgst[SHA1DGSTSZ]; + hmac_sha1(dgst, key, keysz, (uint8_t *)&epoch, sizeof(epoch)); + + int off = dgst[19] & 0x0F; + uint32_t binc = (dgst[off + 0] & 0x7F) << 24 + | (dgst[off + 1] & 0xFF) << 16 + | (dgst[off + 2] & 0xFF) << 8 + | (dgst[off + 3] & 0xFF) << 0; + printf("%0*" PRId32 "\n", digits, binc % pow32(10, digits)); + + if (key != _key) + free(key); +} + +/* TODO: Check for overflow? */ +uint32_t +pow32(uint32_t x, uint32_t y) +{ + uint32_t n = x; + if (y == 0) + return 1; + while (--y != 0) + x *= n; + return x; +} + +bool +xisdigit(char ch) +{ + return ch >= '0' && ch <= '9'; +} + +bool +bigendian(void) +{ + union { + uint16_t u16; + uint8_t u8[2]; + } u = { + .u16 = 0x0102, + }; + u.u16 = 0x0102U; + return u.u8[0] == 1; +} diff --git a/src/sha1-generic.c b/src/sha1-generic.c new file mode 100644 index 0000000..d897a8f --- /dev/null +++ b/src/sha1-generic.c @@ -0,0 +1,78 @@ +#include "common.h" +#include "sha1.h" +#include "xendian.h" + +static inline uint32_t rotl32(uint32_t x, uint8_t bits) + __attribute__((always_inline, const)); + +static const uint32_t K[] = { + 0x5A827999, + 0x6ED9EBA1, + 0x8F1BBCDC, + 0xCA62C1D6, +}; + +void +sha1hashblk(sha1_t *s, const uint8_t *blk) +{ + uint32_t w[80]; + uint32_t a, b, c, d, e, tmp; + + for (int i = 0; i < 16; i++) + w[i] = htobe32(((uint32_t *)blk)[i]); + for (int i = 16; i < 32; i++) + w[i] = rotl32(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1); + for (int i = 32; i < 80; i++) + w[i] = rotl32(w[i-6] ^ w[i-16] ^ w[i-28] ^ w[i-32], 2); + + a = s->dgst[0]; + b = s->dgst[1]; + c = s->dgst[2]; + d = s->dgst[3]; + e = s->dgst[4]; + + for (int i = 0; i < 80; i++) { + uint32_t f, k; + + if (i < 20) { + f = b&c | ~b&d; + k = K[0]; + } else if (i < 40) { + f = b ^ c ^ d; + k = K[1]; + } else if (i < 60) { + f = b&c | b&d | c&d; + k = K[2]; + } else { + f = b ^ c ^ d; + k = K[3]; + } + + tmp = rotl32(a, 5) + f + e + w[i] + k; + e = d; + d = c; + c = rotl32(b, 30); + b = a; + a = tmp; + } + + s->dgst[0] += a; + s->dgst[1] += b; + s->dgst[2] += c; + s->dgst[3] += d; + s->dgst[4] += e; +} + +uint32_t +rotl32(uint32_t x, uint8_t bits) +{ +#if (__GNUC__ || __TINYC__) && __x86_64__ + __asm__ ("roll %1, %0" : "+r" (x) : "c" (bits) : "cc"); + return x; +#elif __GNUC__ && __aarch64__ /* TODO: Test this! */ + __asm__ ("ror %0, %0, %1" : "+r" (x) : "c" (-bits)); + return x; +#else + return (x << bits) | (x >> (32 - bits)); +#endif +} diff --git a/src/sha1-x64.c b/src/sha1-x64.c new file mode 100644 index 0000000..be19ab7 --- /dev/null +++ b/src/sha1-x64.c @@ -0,0 +1,98 @@ +#include <immintrin.h> + +#include "sha1.h" + +#define R(mi, mj, mk, ml, ei, ej, f) \ + do { \ + ei = _mm_sha1nexte_epu32(ei, mi); \ + ej = abcd; \ + mj = _mm_sha1msg2_epu32(mj, mi); \ + abcd = _mm_sha1rnds4_epu32(abcd, ei, f); \ + ml = _mm_sha1msg1_epu32(ml, mi); \ + mk = _mm_xor_si128(mk, mi); \ + } while (0) + +void +sha1hashblk(sha1_t *s, const uint8_t *blk) +{ + __m128i abcd, e0, e1; + __m128i abcd_save, e_save; + __m128i msg0, msg1, msg2, msg3; + + /* Masks for swapping endianness. We make BSWAPDMSK a macro to + please the compiler (it wants immediate values). */ +#define bswapdmsk 0x1B /* 0b00'01'10'11 */ + const __m128i bswapbmsk = _mm_set_epi64x( + 0x0001020304050607ULL, + 0x08090a0b0c0d0e0fULL + ); + + const __m128i *blkx = (const __m128i *)blk; + + abcd = _mm_shuffle_epi32(_mm_loadu_si128((__m128i *)s->dgst), bswapdmsk); + e0 = _mm_set_epi32(s->dgst[4], 0, 0, 0); + + abcd_save = abcd; + e_save = e0; + + /* Rounds 0–3 */ + msg0 = _mm_shuffle_epi8(_mm_loadu_si128(blkx + 0), bswapbmsk); + e0 = _mm_add_epi32(e0, msg0); + e1 = abcd; + abcd = _mm_sha1rnds4_epu32(abcd, e0, 0); + + /* Rounds 4–7 */ + msg1 = _mm_shuffle_epi8(_mm_loadu_si128(blkx + 1), bswapbmsk); + e1 = _mm_sha1nexte_epu32(e1, msg1); + e0 = abcd; + abcd = _mm_sha1rnds4_epu32(abcd, e1, 0); + msg0 = _mm_sha1msg1_epu32(msg0, msg1); + + /* Rounds 8–11 */ + msg2 = _mm_shuffle_epi8(_mm_loadu_si128(blkx + 2), bswapbmsk); + e0 = _mm_sha1nexte_epu32(e0, msg2); + e1 = abcd; + abcd = _mm_sha1rnds4_epu32(abcd, e0, 0); + msg1 = _mm_sha1msg1_epu32(msg1, msg2); + msg0 = _mm_xor_si128(msg0, msg2); + + msg3 = _mm_shuffle_epi8(_mm_loadu_si128(blkx + 3), bswapbmsk); + R(msg3, msg0, msg1, msg2, e1, e0, 0); /* Rounds 12–15 */ + R(msg0, msg1, msg2, msg3, e0, e1, 0); /* Rounds 16–19 */ + R(msg1, msg2, msg3, msg0, e1, e0, 1); /* Rounds 20–23 */ + R(msg2, msg3, msg0, msg1, e0, e1, 1); /* Rounds 24–27 */ + R(msg3, msg0, msg1, msg2, e1, e0, 1); /* Rounds 28–31 */ + R(msg0, msg1, msg2, msg3, e0, e1, 1); /* Rounds 32–35 */ + R(msg1, msg2, msg3, msg0, e1, e0, 1); /* Rounds 36–39 */ + R(msg2, msg3, msg0, msg1, e0, e1, 2); /* Rounds 40–43 */ + R(msg3, msg0, msg1, msg2, e1, e0, 2); /* Rounds 44–47 */ + R(msg0, msg1, msg2, msg3, e0, e1, 2); /* Rounds 48–51 */ + R(msg1, msg2, msg3, msg0, e1, e0, 2); /* Rounds 52–55 */ + R(msg2, msg3, msg0, msg1, e0, e1, 2); /* Rounds 56–59 */ + R(msg3, msg0, msg1, msg2, e1, e0, 3); /* Rounds 60–63 */ + R(msg0, msg1, msg2, msg3, e0, e1, 3); /* Rounds 64–67 */ + + /* Rounds 68–71 */ + e1 = _mm_sha1nexte_epu32(e1, msg1); + e0 = abcd; + msg2 = _mm_sha1msg2_epu32(msg2, msg1); + abcd = _mm_sha1rnds4_epu32(abcd, e1, 3); + msg3 = _mm_xor_si128(msg3, msg1); + + /* Rounds 72–75 */ + e0 = _mm_sha1nexte_epu32(e0, msg2); + e1 = abcd; + msg3 = _mm_sha1msg2_epu32(msg3, msg2); + abcd = _mm_sha1rnds4_epu32(abcd, e0, 3); + + /* Rounds 76–79 */ + e1 = _mm_sha1nexte_epu32(e1, msg3); + e0 = abcd; + abcd = _mm_sha1rnds4_epu32(abcd, e1, 3); + + e0 = _mm_sha1nexte_epu32(e0, e_save); + abcd = _mm_add_epi32(abcd, abcd_save); + + _mm_storeu_si128((__m128i *)s->dgst, _mm_shuffle_epi32(abcd, bswapdmsk)); + s->dgst[4] = _mm_extract_epi32(e0, 3); +} diff --git a/src/sha1.c b/src/sha1.c new file mode 100644 index 0000000..5759991 --- /dev/null +++ b/src/sha1.c @@ -0,0 +1,76 @@ +#include <err.h> +#include <errno.h> +#include <string.h> + +#include "sha1.h" +#include "xendian.h" + +#define lengthof(x) (sizeof(x) / sizeof(*(x))) +#define MIN(x, y) ((x) < (y) ? (x) : (y)) + +void sha1hashblk(sha1_t *, const uint8_t *); + +void +sha1init(sha1_t *s) +{ + static const uint32_t H[] = { + 0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0, + }; + memcpy(s->dgst, H, sizeof(H)); + s->msgsz = s->bufsz = 0; +} + +void +sha1hash(sha1_t *s, const uint8_t *msg, size_t msgsz) +{ + if (s->msgsz + (msgsz * 8) < s->msgsz) { + errno = EOVERFLOW; + err(1, "sha1"); + } + + s->msgsz += msgsz * 8; + + while (msgsz != 0) { + size_t free_space = SHA1BLKSZ - s->bufsz; + size_t ncpy = MIN(msgsz, free_space); + memcpy(s->buf + s->bufsz, msg, ncpy); + s->bufsz += ncpy; + msg += ncpy; + msgsz -= ncpy; + + if (s->bufsz == SHA1BLKSZ) { + sha1hashblk(s, s->buf); + s->bufsz = 0; + } + } +} + +void +sha1end(sha1_t *s, uint8_t *dgst) +{ + s->buf[s->bufsz++] = 0x80; + + if (s->bufsz > SHA1BLKSZ - sizeof(uint64_t)) { + while (s->bufsz < SHA1BLKSZ) + s->buf[s->bufsz++] = 0; + sha1hashblk(s, s->buf); + s->bufsz = 0; + } + + while (s->bufsz < 56) + s->buf[s->bufsz++] = 0; + uint64_t n = htobe64(s->msgsz); + memcpy(s->buf + (SHA1BLKSZ/8 - 1)*sizeof(uint64_t), &n, sizeof(n)); + + sha1hashblk(s, s->buf); + + for (size_t i = 0; i < lengthof(s->dgst); i++) { + /* Pretty please compiler optimize this */ + uint32_t n = htobe32(s->dgst[i]); + memcpy(dgst + i*sizeof(uint32_t), &n, sizeof(n)); + } +} diff --git a/src/sha1.h b/src/sha1.h new file mode 100644 index 0000000..ea08d37 --- /dev/null +++ b/src/sha1.h @@ -0,0 +1,21 @@ +#ifndef TOTP_SHA1_H +#define TOTP_SHA1_H + +#include <stddef.h> +#include <stdint.h> + +#define SHA1DGSTSZ (20) +#define SHA1BLKSZ (64) + +typedef struct { + uint32_t dgst[SHA1DGSTSZ / sizeof(uint32_t)]; + uint64_t msgsz; + uint8_t buf[SHA1BLKSZ]; + size_t bufsz; +} sha1_t; + +void sha1init(sha1_t *); +void sha1hash(sha1_t *, const uint8_t *, size_t); +void sha1end(sha1_t *, uint8_t *); + +#endif /* !TOTP_SHA1_H */ diff --git a/src/xendian.h b/src/xendian.h new file mode 100644 index 0000000..b43661f --- /dev/null +++ b/src/xendian.h @@ -0,0 +1,21 @@ +#ifndef TOTP_XENDIAN_H +#define TOTP_XENDIAN_H + +/* This header grabs the htobe64() and co. functions in a more + cross-platform manner. In general you will find these functions in + <sys/endian.h>, however Linux and OpenBSD include them in <endian.h>. + To make things even better this header doesn’t exist on MacOS so we + need to define wrapper macros for the htonXX() functions from + <arpa/inet.h>. */ + +#if defined(__OpenBSD__) || defined(__linux__) +# include <endian.h> +#elif defined(__APPLE__) +# include <arpa/inet.h> +# define htobe32(x) htonl(x) +# define htobe64(x) htonll(x) +#else +# include <sys/endian.h> +#endif + +#endif /* !TOTP_XENDIAN_H */ |