--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+#ifndef __ARGS_H_
+#define __ARGS_H_
+
+/* needed for strptime */
+#define _XOPEN_SOURCE
+#define _POSIX_C_SOURCE 200809L
+
+#include<getopt.h>
+#include<string.h>
+#include<time.h>
+
+#include<totp.h>
+#include<usage.h>
+
+int args(int, char**);
+
+#endif
--- /dev/null
+#ifndef __BASE32_H_
+#define __BASE32_H_
+
+#include<stdio.h>
+#include<stdlib.h>
+
+int from_base32(unsigned char**, ssize_t*);
+
+#endif
--- /dev/null
+#ifndef __MAIN_H_
+#define __MAIN_H_
+
+#include<getopt.h>
+#include<stdio.h>
+#include<stdlib.h>
+
+#include<args.h>
+#include<base32.h>
+#include<totp.h>
+
+int main(int,char**);
+
+#endif
--- /dev/null
+#ifndef __TOTP_H_
+#define __TOTP_H_
+
+#include<math.h>
+#include<stdio.h>
+#include<stdlib.h>
+#include<time.h>
+
+#include<openssl/evp.h>
+#include<openssl/hmac.h>
+#include<openssl/sha.h>
+
+extern time_t now;
+
+int totp(unsigned char*, ssize_t, uint32_t*);
+
+#endif
--- /dev/null
+#ifndef __USAGE_H_
+#define __USAGE_H_
+
+#include<stdio.h>
+
+void usage();
+
+#endif
--- /dev/null
+(specifications->manifest (list "autoconf"
+ "automake"
+ "coreutils"
+ "gawk"
+ "gcc-toolchain"
+ "gdb"
+ "grep"
+ "make"
+ "openssl"
+ "sed"
+ "valgrind"))
--- /dev/null
+#include<args.h>
+
+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;
+}
--- /dev/null
+#include<base32.h>
+
+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;
+}
--- /dev/null
+#include<main.h>
+
+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;
+}
--- /dev/null
+#include<totp.h>
+
+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<sizeof(time_t); i++) {
+ data[sizeof(time_t) - (1 + i)] = now >> (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;
+}
--- /dev/null
+#include<usage.h>
+
+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");
+}
--- /dev/null
+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
--- /dev/null
+#include<base32.tests.h>
+
+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;
+}
--- /dev/null
+#ifndef BASE32_TESTS_H_
+#define BASE32_TESTS_H_
+
+#include<test_utils.h>
+
+#include<base32.h>
+
+int main();
+
+#endif
--- /dev/null
+#ifndef __TEST_UTILS_H_
+#define __TEST_UTILS_H_
+
+#include<assert.h>
+#include<stdlib.h>
+#include<string.h>
+
+#endif
--- /dev/null
+#include<totp.tests.h>
+
+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;
+}
--- /dev/null
+#ifndef TOTP_TESTS_H_
+#define TOTP_TESTS_H_
+
+#include<test_utils.h>
+
+#include<totp.h>
+
+int main();
+
+#endif