From 21731dd45eced03fe5dde6b7421c778f97a09555 Mon Sep 17 00:00:00 2001 From: Ori Bernstein Date: Wed, 25 Dec 2024 02:18:43 +0000 Subject: [PATCH] auth/factotum: add support for TOTP code generation --- sys/man/4/factotum | 19 +++- sys/src/cmd/auth/factotum/dat.h | 1 + sys/src/cmd/auth/factotum/fs.c | 1 + sys/src/cmd/auth/factotum/mkfile | 1 + sys/src/cmd/auth/factotum/totp.c | 146 +++++++++++++++++++++++++++++++ sys/src/cmd/auth/mkfile | 1 + sys/src/cmd/auth/totp.c | 48 ++++++++++ 7 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 sys/src/cmd/auth/factotum/totp.c create mode 100644 sys/src/cmd/auth/totp.c diff --git a/sys/man/4/factotum b/sys/man/4/factotum index 05ffe1fb9..d33de4fce 100644 --- a/sys/man/4/factotum +++ b/sys/man/4/factotum @@ -1,6 +1,6 @@ .TH FACTOTUM 4 .SH NAME -factotum, fgui, userpasswd \- authentication agent +factotum, fgui, userpasswd, totp \- authentication agent .SH SYNOPSIS .B auth/factotum [ @@ -26,6 +26,14 @@ factotum, fgui, userpasswd \- authentication agent .PP .B auth/userpasswd .I fmt +.PP +.B auth/totp +[ +.B -k +.I pattern +] | [ +.B label +] .SH DESCRIPTION .I Factotum is a user-level file system that @@ -258,6 +266,15 @@ key tuple specified in .IR fmt . This can be used by shell scripts to do cleartext password authentication. +.PP +.I Totp +queries and prints an +.B RFC 6238 + TOTP code +for the +.B proto=totp +key tuple specified. +This can be used to authenticate with services that require time based OTP. .SS "Key Tuples .PP A diff --git a/sys/src/cmd/auth/factotum/dat.h b/sys/src/cmd/auth/factotum/dat.h index d51bf919c..7bf478771 100644 --- a/sys/src/cmd/auth/factotum/dat.h +++ b/sys/src/cmd/auth/factotum/dat.h @@ -228,3 +228,4 @@ extern Proto rsa; /* rsa.c */ extern Proto httpdigest; /* httpdigest.c */ extern Proto ecdsa; /* ecdsa.c */ extern Proto wpapsk; /* wpapsk.c */ +extern Proto totp; /* totp */ diff --git a/sys/src/cmd/auth/factotum/fs.c b/sys/src/cmd/auth/factotum/fs.c index 6926e633e..126ac9835 100644 --- a/sys/src/cmd/auth/factotum/fs.c +++ b/sys/src/cmd/auth/factotum/fs.c @@ -43,6 +43,7 @@ prototab[] = &vnc, &ecdsa, &wpapsk, + &totp, nil, }; diff --git a/sys/src/cmd/auth/factotum/mkfile b/sys/src/cmd/auth/factotum/mkfile index 5b6186e7a..7d22a32ab 100644 --- a/sys/src/cmd/auth/factotum/mkfile +++ b/sys/src/cmd/auth/factotum/mkfile @@ -14,6 +14,7 @@ PROTO=\ rsa.$O\ ecdsa.$O\ wpapsk.$O\ + totp.$O\ FOFILES=\ $PROTO\ diff --git a/sys/src/cmd/auth/factotum/totp.c b/sys/src/cmd/auth/factotum/totp.c new file mode 100644 index 000000000..1dd630e10 --- /dev/null +++ b/sys/src/cmd/auth/factotum/totp.c @@ -0,0 +1,146 @@ +#include "dat.h" + +typedef struct State State; +struct State { + Key *key; +}; + +enum { + HaveTotp, + Maxphase, +}; + +enum { + Maxdigits = 8, + Sec = 1000*1000*1000, +}; + +static char *phasenames[Maxphase] ={ + [HaveTotp] "HaveTotp", +}; + +static int +genhotp(uchar *key, int n, uvlong c, int len) +{ + uchar hash[SHA1dlen]; + uchar data[8]; + u32int h, m; + int o; + + data[0] = (c>>56) & 0xff; + data[1] = (c>>48) & 0xff; + data[2] = (c>>40) & 0xff; + data[3] = (c>>32) & 0xff; + data[4] = (c>>24) & 0xff; + data[5] = (c>>16) & 0xff; + data[6] = (c>> 8) & 0xff; + data[7] = (c>> 0) & 0xff; + hmac_sha1(data, sizeof(data), key, n, hash, nil); + + o = hash[SHA1dlen - 1] & 0x0F; + h = ((hash[o] & 0x7F) << 24) + | (hash[o + 1] & 0xFF) << 16 + | (hash[o + 2] & 0xFF) << 8 + | hash[o + 3] & 0xFF; + m = 1; + while(len-- > 0) + m *= 10; + return h % m; +} + +static int +gentotp(char *secret, vlong t, int len, vlong period) +{ + uchar key[512]; + int n; + + n = dec32(key, sizeof(key), secret, strlen(secret)); + if(n < 0){ + werrstr("invalid totp secret"); + return -1; + } + return genhotp(key, n, t/period, len); +} + +static int +totpinit(Proto *p, Fsstate *fss) +{ + int ret; + Key *k; + Keyinfo ki; + State *s; + + ret = findkey(&k, mkkeyinfo(&ki, fss, nil), "%s", p->keyprompt); + if(ret != RpcOk) + return ret; + setattrs(fss->attr, k->attr); + s = emalloc(sizeof(*s)); + s->key = k; + fss->ps = s; + fss->phase = HaveTotp; + return RpcOk; +} + +static void +totpclose(Fsstate *fss) +{ + State *s; + + s = fss->ps; + if(s->key) + closekey(s->key); + free(s); +} + +static int +totpread(Fsstate *fss, void *va, uint *n) +{ + char *secret, *digits, *period; + int len, otp; + vlong tdiv; + State *s; + + s = fss->ps; + len = 6; + tdiv = 30ULL*Sec; + switch(fss->phase){ + default: + return phaseerror(fss, "read"); + + case HaveTotp: + digits = _strfindattr(s->key->attr, "digits"); + secret = _strfindattr(s->key->privattr, "!secret"); + period = _strfindattr(s->key->attr, "period"); + if(secret==nil) + return failure(fss, "missing totp secret"); + if(digits != nil) + len = atoi(digits); + if(period != nil) + tdiv = strtoll(period, nil, 0)*Sec; + if(*n < len) + return toosmall(fss, len); + if(len < 1 || len > Maxdigits || tdiv <= 0) + return failure(fss, "too many digits"); + otp = gentotp(secret, nsec(), len, tdiv); + if(otp < 0) + return failure(fss, "%r"); + *n = snprint(va, *n, "%.*d", len, otp); + return RpcOk; + } +} + +static int +totpwrite(Fsstate *fss, void*, uint) +{ + return phaseerror(fss, "write"); +} + +Proto totp = { + .name= "totp", + .init= totpinit, + .write= totpwrite, + .read= totpread, + .close= totpclose, + .addkey= replacekey, + .keyprompt= "label? !secret?", +}; diff --git a/sys/src/cmd/auth/mkfile b/sys/src/cmd/auth/mkfile index 25c4fe2d3..6bf149541 100644 --- a/sys/src/cmd/auth/mkfile +++ b/sys/src/cmd/auth/mkfile @@ -35,6 +35,7 @@ TARG=\ rsafill\ rsagen\ ssh2rsa\ + totp\ uniq\ userpasswd\ warning\ diff --git a/sys/src/cmd/auth/totp.c b/sys/src/cmd/auth/totp.c new file mode 100644 index 000000000..ce54b6fb5 --- /dev/null +++ b/sys/src/cmd/auth/totp.c @@ -0,0 +1,48 @@ +#include +#include +#include + +char *keypat; + +void +usage(void) +{ + fprint(2, "usage: %s fmt\n", argv0); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + char params[512]; + AuthRpc *rpc; + int fd; + + ARGBEGIN{ + case 'k': + keypat = EARGF(usage()); + break; + default: + usage(); + }ARGEND + + quotefmtinstall(); + if(keypat == nil) + snprint(params, sizeof(params), "proto=totp label=%q", argv[0]); + else + snprint(params, sizeof(params), "proto=totp %s", keypat); + + if((fd = open("/mnt/factotum/rpc", ORDWR|OCEXEC)) == -1) + sysfatal("open /mnt/factotum/rpc: %r"); + if((rpc = auth_allocrpc(fd)) == nil) + sysfatal("allocrpc: %r"); + if(auth_rpc(rpc, "start", params, strlen(params)) != ARok + || auth_rpc(rpc, "read", nil, 0) != ARok) + sysfatal("totp proto: %r"); + rpc->arg[rpc->narg] = '\0'; + print("%s\n", rpc->arg); + + close(fd); + auth_freerpc(rpc); + exits(nil); +}