ref: 21731dd45eced03fe5dde6b7421c778f97a09555
parent: 1a919327313d87ca7797928b3cbad80cb40d5167
author: Ori Bernstein <ori@eigenstate.org>
date: Tue Dec 24 21:18:43 EST 2024
auth/factotum: add support for TOTP code generation
--- 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 @@
.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 @@
.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
--- a/sys/src/cmd/auth/factotum/dat.h
+++ b/sys/src/cmd/auth/factotum/dat.h
@@ -228,3 +228,4 @@
extern Proto httpdigest; /* httpdigest.c */
extern Proto ecdsa; /* ecdsa.c */
extern Proto wpapsk; /* wpapsk.c */
+extern Proto totp; /* totp */
--- a/sys/src/cmd/auth/factotum/fs.c
+++ b/sys/src/cmd/auth/factotum/fs.c
@@ -43,6 +43,7 @@
&vnc,
&ecdsa,
&wpapsk,
+ &totp,
nil,
};
--- a/sys/src/cmd/auth/factotum/mkfile
+++ b/sys/src/cmd/auth/factotum/mkfile
@@ -14,6 +14,7 @@
rsa.$O\
ecdsa.$O\
wpapsk.$O\
+ totp.$O\
FOFILES=\
$PROTO\
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/totp.c
@@ -1,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?",
+};
--- a/sys/src/cmd/auth/mkfile
+++ b/sys/src/cmd/auth/mkfile
@@ -35,6 +35,7 @@
rsafill\
rsagen\
ssh2rsa\
+ totp\
uniq\
userpasswd\
warning\
--- /dev/null
+++ b/sys/src/cmd/auth/totp.c
@@ -1,0 +1,48 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+
+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);
+}
--
⑨