project creation master v1.0.0
authoralex <[email protected]>
Wed, 15 Jan 2025 16:06:18 +0000 (08:06 -0800)
committeralex <[email protected]>
Sat, 18 Jan 2025 08:34:02 +0000 (00:34 -0800)
initial design complete

20 files changed:
.gitignore [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
configure.ac [new file with mode: 0644]
inc/args.h [new file with mode: 0644]
inc/base32.h [new file with mode: 0644]
inc/main.h [new file with mode: 0644]
inc/totp.h [new file with mode: 0644]
inc/usage.h [new file with mode: 0644]
manifest.scm [new file with mode: 0644]
src/args.c [new file with mode: 0644]
src/base32.c [new file with mode: 0644]
src/main.c [new file with mode: 0644]
src/totp.c [new file with mode: 0644]
src/usage.c [new file with mode: 0644]
test/Makefile.am [new file with mode: 0644]
test/base32.tests.c [new file with mode: 0644]
test/base32.tests.h [new file with mode: 0644]
test/test_utils.h [new file with mode: 0644]
test/totp.tests.c [new file with mode: 0644]
test/totp.tests.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..7cc5ee7
--- /dev/null
@@ -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 (file)
index 0000000..8c4aa60
--- /dev/null
@@ -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 (file)
index 0000000..d2d12b3
--- /dev/null
@@ -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 (file)
index 0000000..ebc3cdd
--- /dev/null
@@ -0,0 +1,17 @@
+#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
diff --git a/inc/base32.h b/inc/base32.h
new file mode 100644 (file)
index 0000000..c75ccc0
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef __BASE32_H_
+#define __BASE32_H_
+
+#include<stdio.h>
+#include<stdlib.h>
+
+int from_base32(unsigned char**, ssize_t*);
+
+#endif
diff --git a/inc/main.h b/inc/main.h
new file mode 100644 (file)
index 0000000..cf632a2
--- /dev/null
@@ -0,0 +1,14 @@
+#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
diff --git a/inc/totp.h b/inc/totp.h
new file mode 100644 (file)
index 0000000..69505be
--- /dev/null
@@ -0,0 +1,17 @@
+#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
diff --git a/inc/usage.h b/inc/usage.h
new file mode 100644 (file)
index 0000000..c05b501
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef __USAGE_H_
+#define __USAGE_H_
+
+#include<stdio.h>
+
+void usage();
+
+#endif
diff --git a/manifest.scm b/manifest.scm
new file mode 100644 (file)
index 0000000..6c53f86
--- /dev/null
@@ -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 (file)
index 0000000..1ae55f1
--- /dev/null
@@ -0,0 +1,50 @@
+#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;
+}
diff --git a/src/base32.c b/src/base32.c
new file mode 100644 (file)
index 0000000..500211e
--- /dev/null
@@ -0,0 +1,66 @@
+#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;
+}
diff --git a/src/main.c b/src/main.c
new file mode 100644 (file)
index 0000000..dc228b0
--- /dev/null
@@ -0,0 +1,34 @@
+#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;
+}
diff --git a/src/totp.c b/src/totp.c
new file mode 100644 (file)
index 0000000..d8c8c6e
--- /dev/null
@@ -0,0 +1,36 @@
+#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;
+}
diff --git a/src/usage.c b/src/usage.c
new file mode 100644 (file)
index 0000000..8500a3e
--- /dev/null
@@ -0,0 +1,8 @@
+#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");
+}
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644 (file)
index 0000000..795b477
--- /dev/null
@@ -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 (file)
index 0000000..4d7fbc3
--- /dev/null
@@ -0,0 +1,91 @@
+#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;
+}
diff --git a/test/base32.tests.h b/test/base32.tests.h
new file mode 100644 (file)
index 0000000..9757bab
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef BASE32_TESTS_H_
+#define BASE32_TESTS_H_
+
+#include<test_utils.h>
+
+#include<base32.h>
+
+int main();
+
+#endif
diff --git a/test/test_utils.h b/test/test_utils.h
new file mode 100644 (file)
index 0000000..5b688e9
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef __TEST_UTILS_H_
+#define __TEST_UTILS_H_
+
+#include<assert.h>
+#include<stdlib.h>
+#include<string.h>
+
+#endif
diff --git a/test/totp.tests.c b/test/totp.tests.c
new file mode 100644 (file)
index 0000000..36e5346
--- /dev/null
@@ -0,0 +1,34 @@
+#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;
+}
diff --git a/test/totp.tests.h b/test/totp.tests.h
new file mode 100644 (file)
index 0000000..de74f8d
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef TOTP_TESTS_H_
+#define TOTP_TESTS_H_
+
+#include<test_utils.h>
+
+#include<totp.h>
+
+int main();
+
+#endif