From: alex Date: Wed, 15 Jan 2025 16:06:18 +0000 (-0800) Subject: project creation X-Git-Tag: v1.0.0^0 X-Git-Url: http://git.infiniteadaptability.org/?a=commitdiff_plain;h=HEAD;p=totp project creation initial design complete --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7cc5ee7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +totp +*.o +*.tests + +# autoconf/automake +*.log +*.trs +.deps +.dirstamp +.lineno +aclocal.m4 +autom4te.cache/ +autoscan.log +build-aux/ +config.h +config.h.in +config.h.in~ +config.log +config.status +configure +configure~ +configure.scan +Makefile +Makefile.in +stamp-h1 diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..8c4aa60 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,20 @@ +AM_CPPFLAGS = \ + -Wall \ + -Werror + +bin_PROGRAMS = totp +totp_SOURCES = \ + src/args.c \ + src/base32.c \ + src/main.c \ + src/totp.c \ + src/usage.c + +totp_SOURCES += \ + inc/args.h \ + inc/base32.h \ + inc/main.h \ + inc/totp.h \ + inc/usage.h + +SUBDIRS = . test diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..d2d12b3 --- /dev/null +++ b/configure.ac @@ -0,0 +1,73 @@ +AC_PREREQ([2.69]) +AC_INIT([totp], [1.0.0]) + +# Store build files not in main directory +AC_CONFIG_AUX_DIR([build-aux]) + +AM_INIT_AUTOMAKE([foreign subdir-objects -Wall -Werror]) + +AC_CONFIG_SRCDIR([src/main.c]) +AC_CONFIG_HEADERS([inc/config.h]) + +AC_ARG_ENABLE([debug], + [AS_HELP_STRING([--enable-debug], + [enable debugging])], + [enable_debug=$enableval], + [enable_debug=no]) + +AC_ARG_ENABLE([memcheck], + [AS_HELP_STRING([--disable-memcheck], + [disable valgrind (enabled by default)])], + [enable_memcheck=$enableval], + [enable_memcheck=yes]) + +AC_PATH_PROG([VALGRIND], [valgrind]) +AM_CONDITIONAL([HAVE_VALGRIND], [test -n "$VALGRIND"]) + +AC_MSG_CHECKING([if debugging]) +if test x$enable_debug != xno; then + AC_MSG_RESULT(yes) + CFLAGS="-ggdb3 -O0" +else + AC_MSG_RESULT(no) +fi + +AM_CONDITIONAL([ENABLE_DEBUG],[test x$enable_debug != xno]) + +dnl disable memcheck if valgrind not found +if test "x$enable_memcheck" != "xno"; then + if test -z "$VALGRIND"; then + enable_memcheck=no + fi +fi + +AC_MSG_CHECKING([if memcheck should be enabled]) +if test x$enable_memcheck != xno; then + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi + +AM_CONDITIONAL([ENABLE_MEMCHECK],[test x$enable_memcheck = xyes]) + +# Checks for programs. +AC_PROG_CC +AC_PROG_INSTALL + +# Checks for libraries. +AC_CHECK_LIB([crypto], [HMAC]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T +AC_TYPE_UINT32_T + +# Checks for libraries. +AC_FUNC_MALLOC +AC_FUNC_MKTIME +AC_CHECK_FUNCS([floor memset]) + +AC_CONFIG_FILES([Makefile + test/Makefile]) + +AC_OUTPUT diff --git a/inc/args.h b/inc/args.h new file mode 100644 index 0000000..ebc3cdd --- /dev/null +++ b/inc/args.h @@ -0,0 +1,17 @@ +#ifndef __ARGS_H_ +#define __ARGS_H_ + +/* needed for strptime */ +#define _XOPEN_SOURCE +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include + +#include +#include + +int args(int, char**); + +#endif diff --git a/inc/base32.h b/inc/base32.h new file mode 100644 index 0000000..c75ccc0 --- /dev/null +++ b/inc/base32.h @@ -0,0 +1,9 @@ +#ifndef __BASE32_H_ +#define __BASE32_H_ + +#include +#include + +int from_base32(unsigned char**, ssize_t*); + +#endif diff --git a/inc/main.h b/inc/main.h new file mode 100644 index 0000000..cf632a2 --- /dev/null +++ b/inc/main.h @@ -0,0 +1,14 @@ +#ifndef __MAIN_H_ +#define __MAIN_H_ + +#include +#include +#include + +#include +#include +#include + +int main(int,char**); + +#endif diff --git a/inc/totp.h b/inc/totp.h new file mode 100644 index 0000000..69505be --- /dev/null +++ b/inc/totp.h @@ -0,0 +1,17 @@ +#ifndef __TOTP_H_ +#define __TOTP_H_ + +#include +#include +#include +#include + +#include +#include +#include + +extern time_t now; + +int totp(unsigned char*, ssize_t, uint32_t*); + +#endif diff --git a/inc/usage.h b/inc/usage.h new file mode 100644 index 0000000..c05b501 --- /dev/null +++ b/inc/usage.h @@ -0,0 +1,8 @@ +#ifndef __USAGE_H_ +#define __USAGE_H_ + +#include + +void usage(); + +#endif diff --git a/manifest.scm b/manifest.scm new file mode 100644 index 0000000..6c53f86 --- /dev/null +++ b/manifest.scm @@ -0,0 +1,11 @@ +(specifications->manifest (list "autoconf" + "automake" + "coreutils" + "gawk" + "gcc-toolchain" + "gdb" + "grep" + "make" + "openssl" + "sed" + "valgrind")) diff --git a/src/args.c b/src/args.c new file mode 100644 index 0000000..1ae55f1 --- /dev/null +++ b/src/args.c @@ -0,0 +1,50 @@ +#include + +static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"time", required_argument, 0, 't'}, + {0,0,0,0} +}; + +time_t now = 0; + +int args(int argc, char **argv) { + struct tm tm; + int c; + + while(1) { + int option_index = 0; + + if((c = getopt_long(argc, argv, "ht:", long_options, &option_index)) == -1) { break; } + + switch(c) { + case 0: + if(long_options[option_index].flag != 0) { break; } + + fprintf(stderr, "option %s", long_options[option_index].name); + if(optarg) { + fprintf(stderr, " with arg %s", optarg); + } + fprintf(stderr, "\n"); + return -1; + + break; + case 'h': + usage(); + return -1; + case 't': + memset(&tm, 0, sizeof(tm)); + + if(strptime(optarg, "%F %T %z", &tm) != NULL) { + now = mktime(&tm); + } + + break; + case '?': + default: + return -1; + } + } + + return 1; +} diff --git a/src/base32.c b/src/base32.c new file mode 100644 index 0000000..500211e --- /dev/null +++ b/src/base32.c @@ -0,0 +1,66 @@ +#include + +int from_base32(unsigned char **raw, ssize_t *n) { + unsigned char *buf, *p; + char c, to_set; + size_t i, j, offset, len; + + if(NULL == raw || NULL == *raw) { + fprintf(stderr, "invalid buffer\n"); + return -1; + } + + if(NULL == n || *n <= 0) { + fprintf(stderr, "invalid secret size\n"); + return -1; + } + + len = ((*n)*5)>>3; + if(len == 0) { + fprintf(stderr, "invalid secret size\n"); + return -1; + } + + buf = calloc(len, sizeof(char)); + if(NULL == buf) { + perror("calloc"); + return -1; + } + + offset = 0; + p = *raw; + j = 0; + for(i = 0; i < *n; i++) { + c = p[i]; + + if(c >= 'A' && c <= 'Z') { + to_set = c - 65; + } else if(c >= '2' && c <= '7') { + to_set = c - 24; + } else if(c == '=') { + continue; + } else { + fprintf(stderr, "invalid base32 character %c\n", c); + return -1; + } + + // No need to mask this value since it will always be <31 + buf[j] += (to_set << 3) >> offset; + + if(((offset + 5) > 7) && (j + 1 < len)) { + j++; + buf[j] += ((to_set << (8 - (5 - (8 - offset)))) & 0xff); + } + + offset += 5; + offset %= 8; + } + + p = *raw; + *raw = buf; + *n = len; + + free(p); + + return 1; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..dc228b0 --- /dev/null +++ b/src/main.c @@ -0,0 +1,34 @@ +#include + +int main(int argc, char **argv) { + unsigned char *buf; + size_t len; + ssize_t n; + uint32_t code; + + buf = NULL; + now = 0; + + if(args(argc, argv)<0) { return EXIT_FAILURE; } + + if((n = getline((char **)&buf, &len, stdin)) <= 0) { + perror("getline failed"); + fprintf(stderr, "No secret provided\n"); + goto clean; + } + + // Trim trailing '\n' + n--; + + if(from_base32(&buf, &n) < 0) { goto clean; } + if(totp((unsigned char *)buf, n, &code) < 0) { goto clean; } + + fprintf(stdout, "%.6u\n", code % 1000000); + + free(buf); + + return EXIT_SUCCESS; + clean: + if(buf != NULL) { free(buf); } + return EXIT_FAILURE; +} diff --git a/src/totp.c b/src/totp.c new file mode 100644 index 0000000..d8c8c6e --- /dev/null +++ b/src/totp.c @@ -0,0 +1,36 @@ +#include + +int totp(unsigned char *key, ssize_t n, uint32_t *code) { + unsigned char hash[SHA_DIGEST_LENGTH]; + unsigned char data[sizeof(time_t)]; + size_t offset; + unsigned int hash_len = SHA_DIGEST_LENGTH; + + if(NULL == key) { return -1; } + if(n <= 0) { return -1; } + + if(now == 0) { + now = time(NULL); + } + + now = floor(now / 30); + + // Guarantee that the data buffer is in big-endian order + for(size_t i = 0; i> (8*i) & 0xff; + } + + if(NULL == HMAC(EVP_sha1(), key, n, data, sizeof(time_t), hash, &hash_len)) { + fprintf(stderr, "hashing failed\n"); + return -1; + } + + offset = hash[SHA_DIGEST_LENGTH - 1] & 0xf; + + (*code) = ((hash[offset] & 0x7f) << 24) + | ((hash[offset+1] & 0xff) << 16) + | ((hash[offset+2] & 0xff) << 8) + | (hash[offset+3] & 0xff); + + return 1; +} diff --git a/src/usage.c b/src/usage.c new file mode 100644 index 0000000..8500a3e --- /dev/null +++ b/src/usage.c @@ -0,0 +1,8 @@ +#include + +void usage() { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, "\t[SECRET] | totp\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Pass a base32 encoded secrets to stdin and generate a totp code at the current time.\n"); +} diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 0000000..795b477 --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,23 @@ +AM_CPPFLAGS = \ + -Wall \ + -Werror + +EXTRA_DIST = \ + test_utils.h \ + base32.tests.h + +check_PROGRAMS = base32.tests totp.tests +TESTS = $(check_PROGRAMS) + +if ENABLE_MEMCHECK +LOG_COMPILER = $(VALGRIND) +AM_LOG_FLAGS = --leak-check=full -v --track-origins=yes --error-exitcode=1 +endif + +base32_tests_SOURCES = \ + base32.tests.c \ + $(top_srcdir)/src/base32.c + +totp_tests_SOURCES = \ + totp.tests.c \ + $(top_srcdir)/src/totp.c diff --git a/test/base32.tests.c b/test/base32.tests.c new file mode 100644 index 0000000..4d7fbc3 --- /dev/null +++ b/test/base32.tests.c @@ -0,0 +1,91 @@ +#include + +int main() { + unsigned char *buf; + ssize_t n; + + buf = NULL; + n = 0; + + assert(from_base32(NULL, NULL) == -1); + assert(from_base32(&buf, &n) == -1); + + buf = malloc(3); + assert(buf!=NULL); + strcpy((char *)buf, "XY"); + + assert(from_base32(&buf, &n) == -1); + + n = 2; + assert(from_base32(&buf, &n) == 1); + assert(buf[0] == 190); + assert(n == 1); + free(buf); + + buf = malloc(5); + assert(buf!=NULL); + strcpy((char *)buf, "6LKQ"); + + n = 4; + assert(from_base32(&buf, &n) == 1); + assert(buf[0] == 242); + assert(buf[1] == 213); + assert(n == 2); + free(buf); + + buf = malloc(6); + assert(buf!=NULL); + strcpy((char *)buf, "LQ7YC"); + + n = 5; + assert(from_base32(&buf, &n) == 1); + assert(buf[0] == 92); + assert(buf[1] == 63); + assert(buf[2] == 129); + assert(n == 3); + free(buf); + + buf = malloc(8); + assert(buf!=NULL); + strcpy((char *)buf, "TOA5RWI"); + + n = 7; + assert(from_base32(&buf, &n) == 1); + assert(buf[0] == 155); + assert(buf[1] == 129); + assert(buf[2] == 216); + assert(buf[3] == 217); + assert(n == 4); + free(buf); + + buf = malloc(14); + assert(buf!=NULL); + strcpy((char *)buf, "RZLJOCOTSPYRC"); + + n = 13; + assert(from_base32(&buf, &n) == 1); + assert(buf[0] == 142); + assert(buf[1] == 86); + assert(buf[2] == 151); + assert(buf[3] == 9); + assert(buf[4] == 211); + assert(buf[5] == 147); + assert(buf[6] == 241); + assert(buf[7] == 17); + assert(n == 8); + free(buf); + + buf = malloc(53); + assert(buf!=NULL); + strcpy((char *)buf, "4KFCI2A3CLB2FV7MAYKSYAXNKFIYH6BOP5UEOOGCF6MLEXTU6KGA"); + + n = 52; + assert(from_base32(&buf, &n) == 1); + unsigned char expected[] = { 226, 138, 36, 104, 27, 18, 195, 162, 215, 236, 6, 21, 44, 2, 237, 81, 81, 131, + 248, 46, 127, 104, 71, 56, 194, 47, 152, 178, 94, 116, 242, 140 }; + assert(memcmp(buf, expected, 32) == 0); + assert(n == 32); + free(buf); + + return EXIT_SUCCESS; +} diff --git a/test/base32.tests.h b/test/base32.tests.h new file mode 100644 index 0000000..9757bab --- /dev/null +++ b/test/base32.tests.h @@ -0,0 +1,10 @@ +#ifndef BASE32_TESTS_H_ +#define BASE32_TESTS_H_ + +#include + +#include + +int main(); + +#endif diff --git a/test/test_utils.h b/test/test_utils.h new file mode 100644 index 0000000..5b688e9 --- /dev/null +++ b/test/test_utils.h @@ -0,0 +1,8 @@ +#ifndef __TEST_UTILS_H_ +#define __TEST_UTILS_H_ + +#include +#include +#include + +#endif diff --git a/test/totp.tests.c b/test/totp.tests.c new file mode 100644 index 0000000..36e5346 --- /dev/null +++ b/test/totp.tests.c @@ -0,0 +1,34 @@ +#include + +time_t now = 0; + +int main() { + uint32_t code; + unsigned char key[] = "12345678901234567890"; + + now = 59; + assert(totp(key, 20, &code)==1); + assert(code%1000000 == 287082); + + now = 1111111109; + assert(totp(key, 20, &code)==1); + assert(code%1000000 == 81804); + + now = 1111111111; + assert(totp(key, 20, &code)==1); + assert(code%1000000 == 50471); + + now = 1234567890; + assert(totp(key, 20, &code)==1); + assert(code%1000000 == 5924); + + now = 2000000000; + assert(totp(key, 20, &code)==1); + assert(code%1000000 == 279037); + + now = 20000000000; + assert(totp(key, 20, &code)==1); + assert(code%1000000 == 353130); + + return EXIT_SUCCESS; +} diff --git a/test/totp.tests.h b/test/totp.tests.h new file mode 100644 index 0000000..de74f8d --- /dev/null +++ b/test/totp.tests.h @@ -0,0 +1,10 @@ +#ifndef TOTP_TESTS_H_ +#define TOTP_TESTS_H_ + +#include + +#include + +int main(); + +#endif