Initial import
This commit is contained in:
commit
809c2a7811
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
obj
|
17
Makefile
Normal file
17
Makefile
Normal file
@ -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 <bsd.prog.mk>
|
295
login_totp.c
Normal file
295
login_totp.c
Normal file
@ -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
|
||||||
|
* <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <paths.h>
|
||||||
|
#include <readpassphrase.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <login_cap.h>
|
||||||
|
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/hmac.h>
|
||||||
|
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user