From 809c2a78110ddabc7381a175697184f70d9fd383 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 5 Feb 2023 20:33:59 +0000 Subject: [PATCH] Initial import --- .gitignore | 1 + Makefile | 17 +++ login_totp.c | 295 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 313 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 login_totp.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b672fde --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +obj diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f489abf --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +PROG = login_-totp +SRCS = login_totp.c +NOMAN = noman +DPADD += ${LIBCRYPTO} +LDADD += -lcrypto + +CFLAGS += -Wall -Wextra -pedantic + +BINOWN = root +BINGRP = auth +BINMODE = 555 +BINDIR = /usr/libexec/auth + +afterinstall: + ${INSTALL} -d -m 0770 -o ${BINOWN} -g ${BINGRP} /var/db/totp + +.include diff --git a/login_totp.c b/login_totp.c new file mode 100644 index 0000000..35257ee --- /dev/null +++ b/login_totp.c @@ -0,0 +1,295 @@ +/* + * login_totp - TOTP-based login method + * + * Written in 2023 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + + +#define MODE_LOGIN 0 +#define MODE_CHALLENGE 1 +#define MODE_RESPONSE 2 + +#define TOTP_KEYLEN 20 + +#define _PATH_TOTP _PATH_VARDB "totp/" + + +#define PUT64BE(x, o) do { \ + (o)[0] = ((x) >> 56); \ + (o)[1] = ((x) >> 48) & 0xff; \ + (o)[2] = ((x) >> 40) & 0xff; \ + (o)[3] = ((x) >> 32) & 0xff; \ + (o)[4] = ((x) >> 24) & 0xff; \ + (o)[5] = ((x) >> 16) & 0xff; \ + (o)[6] = ((x) >> 8) & 0xff; \ + (o)[7] = (x) & 0xff; \ +} while (0) + +#define GET32BE(o, n) do { \ + (n) = ((o)[0] << 24) | ((o)[1] << 16) | ((o)[2] << 8) | \ + ((o)[3]); \ +} while (0) + + +static int32_t totp(const void *, int, int, const EVP_MD *, uint64_t, + uint64_t); +static int32_t totp_login(char *, char *); +static void user_key(char *, unsigned char *); + +static int32_t +totp(const void *key, int key_len, int digits, const EVP_MD *evp_md, + uint64_t ts, uint64_t step) +{ + unsigned char md[EVP_MAX_MD_SIZE]; + uint8_t ctr[8]; + uint64_t counter; + unsigned int md_len, offset; + int32_t value, modulo; + int i; + + /* + * RFC 4226 reads "The HOTP value must be at least a 6-digit value." + * Given that values are AND'd with 0x7fffffff, they're bound by + * 2^31 - 1, 2147483647 in base 10; more than 10 digits is impossible. + */ + if (digits < 6 || digits > 10) + return -1; + + if (digits < 10) { + modulo = 1000000; + for (i = 0; i < digits - 6; i++) + modulo *= 10; + } + + counter = ts / step; + PUT64BE(counter, ctr); + if (HMAC(evp_md, key, key_len, ctr, sizeof(ctr), md, &md_len) == NULL) + return -1; + offset = md[md_len - 1] & 0xf; + GET32BE(&md[offset], value); + value &= 0x7fffffff; + + return digits == 10 ? value : value % modulo; +} + +static int +totp_login(char *user, char *pass) +{ + unsigned char key[TOTP_KEYLEN]; + char buf[16]; + uint64_t ts, step; + time_t now; + int32_t r; + int digits = 6, tries; + + now = time(NULL); + if (now < 0) { + syslog(LOG_WARNING, "time underflow"); + return 0; + } + if (now > INT64_MAX) { + syslog(LOG_WARNING, "time overflow"); + return 0; + } + + ts = (uint64_t)now; + step = 30; + tries = 3; + + if (ts >= step) + ts -= step; + else + tries--; + + user_key(user, &key[0]); + + do { + r = totp(key, sizeof(key), digits, EVP_sha1(), ts, step); + if (r == -1) + syslog(LOG_ERR, "TOTP error"); + else { + snprintf(buf, sizeof(buf), "%0*" PRIi32, digits, r); + if (strcmp(buf, pass) == 0) + return 1; + } + if (ts > UINT64_MAX - step) + break; + ts += step; + } while (--tries > 0); + explicit_bzero(key, sizeof(key)); + + return 0; +} + +static void +user_key(char *user, unsigned char *key) +{ + FILE *f; + char *path; + size_t r; + unsigned char dummy; + + if (asprintf(&path, _PATH_TOTP "%s.key", user) == -1) { + syslog(LOG_ERR, "asprintf: %m"); + exit(1); + } + f = fopen(path, "r"); + if (f == NULL) { + syslog(LOG_ERR, "Can't open keyfile for %s: %m", user); + exit(1); + } + + r = fread(key, sizeof(*key), TOTP_KEYLEN, f); + if (r != TOTP_KEYLEN) { + syslog(LOG_ERR, "short key"); + exit(1); + } + r = fread(&dummy, sizeof(dummy), 1, f); + if (r != 0 || !feof(f)) { + syslog(LOG_ERR, "long key"); + exit(1); + } + + fclose(f); + free(path); +} + +int +main(int argc, char *argv[]) +{ + FILE *back; + char *curtime, *user, *pass; + char pbuf[1024], rbuf[1024]; + time_t clock; + int ch, mode; + + (void)signal(SIGQUIT, SIG_IGN); + (void)signal(SIGINT, SIG_IGN); + + openlog("login_-totp", LOG_ODELAY, LOG_AUTH); + + back = NULL; + mode = MODE_LOGIN; + while ((ch = getopt(argc, argv, "ds:v:")) != -1) { + switch (ch) { + case 'd': + back = stdout; + break; + case 's': + if (strcmp(optarg, "login") == 0) + mode = MODE_LOGIN; + else if (strcmp(optarg, "challenge") == 0) + mode = MODE_CHALLENGE; + else if (strcmp(optarg, "response") == 0) + mode = MODE_RESPONSE; + else { + syslog(LOG_ERR, "%s: invalid service", optarg); + exit(1); + } + break; + case 'v': + break; + default: + syslog(LOG_ERR, "usage error"); + exit(1); + } + } + argc -= optind; + argv += optind; + + switch (argc) { + case 2: + case 1: + user = argv[0]; + break; + default: + syslog(LOG_ERR, "usage error"); + exit(1); + } + + if (pledge("stdio rpath tty", NULL) == -1) { + syslog(LOG_ERR, "pledge: %m"); + exit(1); + } + + if (back == NULL && (back = fdopen(3, "r+")) == NULL) { + syslog(LOG_ERR, "reopening back channel: %m"); + exit(1); + } + + if (mode == MODE_CHALLENGE) { + fprintf(back, BI_VALUE " challenge totp: \n"); + fprintf(back, BI_CHALLENGE "\n"); + exit(0); + } + + pass = NULL; + if (mode == MODE_RESPONSE) { + int i, npass; + + i = -1; + npass = 0; + while (++i < sizeof(rbuf) && read(3, &rbuf[i], 1) == 1) { + if (rbuf[i] == '\0') { + if (strcmp(rbuf, "totp: ") != 0) { + syslog(LOG_ERR, + "%s: unknown challenge\n", rbuf); + exit(1); + } + npass++; + } + if (npass == 2) + break; + if (rbuf[i] == '\0' && npass == 1) + pass = rbuf + i + 1; + } + } else { + pass = readpassphrase("TOTP code: ", pbuf, sizeof(pbuf), + RPP_ECHO_ON); + if (pass == NULL) + goto reject; + } + + if (!totp_login(user, pass)) { + clock = time(NULL); + curtime = ctime(&clock); + if (curtime != NULL) + /* XXX syslog too? */ + fprintf(stderr, "Current time is %s", curtime); + + goto reject; + } + + fprintf(back, BI_AUTH "\n"); + exit(0); + + reject: + fprintf(back, BI_REJECT "\n"); + exit(1); +}