ref: 24bc7678e8c6988da430446e59240f722dbc8043
parent: 8e3d8120a945bad722e218963699f0a17a853d86
author: sirjofri <sirjofri@sirjofri.de>
date: Fri Dec 29 10:22:54 EST 2023
adds patch for factotum
--- /dev/null
+++ b/factotum_totp.diff
@@ -1,0 +1,489 @@
+diff f40225e86cf4b92cab975d9121ff06ed5cfe9d91 uncommitted
+--- 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.c */
+--- 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/test.c
+@@ -1,0 +1,174 @@
++#include <u.h>
++#include <libc.h>
++#include <auth.h>
++
++void
++main(void)
++{
++ int fd;
++ AuthRpc *rpc;
++ int n;
++ uint r;
++ char *s;
++ char response[8192];
++ char *toks[2];
++ char *otp;
++ char *invalid = "000000";
++
++ /******/
++ /* generate OTP */
++
++ fd = open("/mnt/factotum/rpc", ORDWR);
++ if (fd < 0)
++ sysfatal("err: %r");
++
++ rpc = auth_allocrpc(fd);
++ if (!rpc)
++ sysfatal("err: %r");
++
++ s = smprint("proto=totp user=a role=client");
++ n = strlen(s);
++
++ if (auth_rpc(rpc, "start", s, n) != ARok)
++ sysfatal("err: %r");
++
++ r = auth_rpc(rpc, "read", nil, 0);
++ print("response (%d): %s\n", r, rpc->arg);
++
++ if (tokenize(rpc->arg, toks, 2) != 2)
++ sysfatal("err: bad number of args in response!");
++
++ otp = smprint("%s", toks[0]);
++
++ auth_freerpc(rpc);
++ close(fd);
++ print("client success!\n\n");
++ free(s);
++
++ /********/
++ /* valid OTP test */
++
++ fd = open("/mnt/factotum/rpc", ORDWR);
++ if (fd < 0)
++ sysfatal("err: %r");
++
++ rpc = auth_allocrpc(fd);
++ if (!rpc)
++ sysfatal("err: %r");
++
++ s = smprint("proto=totp user=a digits=6 role=server");
++ n = strlen(s);
++
++ if (auth_rpc(rpc, "start", s, n) != ARok)
++ sysfatal("err: %r");
++
++ print("testing %s\n", otp);
++ r = auth_rpc(rpc, "write", otp, strlen(otp));
++ if (r != ARok)
++ sysfatal("err: %r");
++
++ r = auth_rpc(rpc, "read", nil, 0);
++ if (r != ARok)
++ print("valid otp: failed: %s\n\n", rpc->arg);
++ else
++ print("valid otp: success: %s\n\n", rpc->arg);
++
++ auth_freerpc(rpc);
++ close(fd);
++
++ /*******/
++ /* invalid OTP test */
++
++ fd = open("/mnt/factotum/rpc", ORDWR);
++ if (fd < 0)
++ sysfatal("err: %r");
++
++ rpc = auth_allocrpc(fd);
++ if (!rpc)
++ sysfatal("err: %r");
++
++ if (auth_rpc(rpc, "start", s, n) != ARok)
++ sysfatal("err: %r");
++
++ print("testing %s\n", invalid);
++ r = auth_rpc(rpc, "write", invalid, strlen(invalid));
++ if (r != ARok)
++ sysfatal("err: %r");
++
++ r = auth_rpc(rpc, "read", nil, 0);
++ if (r != ARok)
++ print("invalid otp: success: %s\n\n", rpc->arg);
++ else
++ print("invalid otp: failed: %s\n\n", rpc->arg);
++
++ auth_freerpc(rpc);
++ close(fd);
++
++ /******/
++ /* generate OTP with digits */
++
++ fd = open("/mnt/factotum/rpc", ORDWR);
++ if (fd < 0)
++ sysfatal("err: %r");
++
++ rpc = auth_allocrpc(fd);
++ if (!rpc)
++ sysfatal("err: %r");
++
++ s = smprint("proto=totp user=b role=client");
++ n = strlen(s);
++
++ if (auth_rpc(rpc, "start", s, n) != ARok)
++ sysfatal("err: %r");
++
++ free(s);
++ s = smprint("9 10");
++ n = strlen(s);
++
++ if (auth_rpc(rpc, "write", s, n) != ARok)
++ sysfatal("err: %r");
++
++ r = auth_rpc(rpc, "read", nil, 0);
++ print("response (%d): %s\n", r, rpc->arg);
++
++ if (tokenize(rpc->arg, toks, 2) != 2)
++ sysfatal("err: bad number of args in response!");
++
++ otp = smprint("%s", toks[0]);
++
++ auth_freerpc(rpc);
++ close(fd);
++ print("client digits success!\n\n");
++ free(s);
++
++ /********/
++ /* valid OTP test with digits */
++
++ fd = open("/mnt/factotum/rpc", ORDWR);
++ if (fd < 0)
++ sysfatal("err: %r");
++
++ rpc = auth_allocrpc(fd);
++ if (!rpc)
++ sysfatal("err: %r");
++
++ s = smprint("proto=totp user=b digits=9 role=server");
++ n = strlen(s);
++
++ if (auth_rpc(rpc, "start", s, n) != ARok)
++ sysfatal("err: %r");
++
++ print("testing %s\n", otp);
++ r = auth_rpc(rpc, "write", otp, strlen(otp));
++ if (r != ARok)
++ sysfatal("err: %r");
++
++ r = auth_rpc(rpc, "read", nil, 0);
++ if (r != ARok)
++ print("valid otp digits: failed: %s\n\n", rpc->arg);
++ else
++ print("valid otp digits: success: %s\n\n", rpc->arg);
++
++ auth_freerpc(rpc);
++ close(fd);
++}
+--- /dev/null
++++ b/sys/src/cmd/auth/factotum/test.rc
+@@ -1,0 +1,10 @@
++#!/bin/rc
++
++echo 'key proto=totp user=a role=client !secret=abc' >/mnt/factotum/ctl
++echo 'key proto=totp user=a role=server digits=6 !secret=abc' >/mnt/factotum/ctl
++echo 'key proto=totp user=b role=client !secret=def' >/mnt/factotum/ctl
++echo 'key proto=totp user=b role=server digits=9 seconds=10 !secret=def' >/mnt/factotum/ctl
++
++6c -o test.6 test.c
++6l -o 6.test test.6
++6.test
+--- /dev/null
++++ b/sys/src/cmd/auth/factotum/totp.c
+@@ -1,0 +1,268 @@
++/*
++ * TOTP
++ *
++ * Client protocol:
++ * write (optional): digits + seconds
++ * read totp: otp[digits] + time_remaining
++ *
++ * Server protocol:
++ * write totp: otp[digits]
++ * read response: done | error
++ *
++ */
++
++#include "dat.h"
++
++uint dohotp(char *key, uvlong counter);
++uint dototp(char *key, long time, int valid);
++
++char *validstr = "valid";
++int validstrlen = -1;
++
++typedef struct State State;
++struct State
++{
++ Key *key;
++ int valid;
++ int seconds;
++ int digits;
++};
++
++enum
++{
++ HaveTotp,
++ HaveDetails,
++ ValidOtp,
++ InvalidOtp,
++ Maxphase,
++};
++
++static char *phasenames[Maxphase] =
++{
++[HaveTotp] "HaveTotp",
++[HaveDetails] "HaveDetails",
++[ValidOtp] "ValidOtp",
++[InvalidOtp] "InvalidOtp",
++};
++
++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)
++{
++ State *s;
++ char *secret;
++ char decoded[1024];
++ int iscli;
++ uint otp;
++ char *c;
++ int m;
++ long t;
++
++ if (validstrlen < 0)
++ validstrlen = strlen(validstr);
++
++ s = fss->ps;
++ switch (fss->phase) {
++ default:
++ return phaseerror(fss, "read");
++
++ case HaveTotp:
++ iscli = isclient(_strfindattr(s->key->attr, "role"));
++ if (!iscli)
++ return phaseerror(fss, "server protocol must start with a write");
++ s->digits = 6;
++ s->seconds = 30;
++
++ case HaveDetails:
++ iscli = isclient(_strfindattr(s->key->attr, "role"));
++ if (!iscli)
++ return phaseerror(fss, "you found a bug");
++
++ secret = _strfindattr(s->key->privattr, "!secret");
++ dec32((uchar*)decoded, 1024, secret, strlen(secret));
++ t = time(nil);
++ otp = dototp(decoded, t, s->seconds);
++
++ c = smprint("%0*d %ld", s->digits, otp, s->seconds - t%s->seconds);
++
++ m = strlen(c);
++ if (m > *n)
++ return toosmall(fss, m);
++
++ *n = m;
++ memmove(va, c, m);
++ free(c);
++ return RpcOk;
++
++ case ValidOtp:
++ memmove(va, validstr, validstrlen);
++ return RpcOk;
++
++ case InvalidOtp:
++ return failure(fss, "wrong OTP");
++ }
++}
++
++static int
++checkvalid(Fsstate *fss, State *s, char *c, long t, char *decoded, int seconds, int digits, char *entered)
++{
++ uint otp;
++
++ otp = dototp(decoded, t, seconds);
++ snprint(c, digits + 1, "%0*d", digits, otp);
++ if (strcmp(c, entered) == 0) {
++ free(c);
++ fss->phase = ValidOtp;
++ s->valid = 1;
++ return 1;
++ }
++ return 0;
++}
++
++static int
++totpwrite(Fsstate *fss, void *va, uint n)
++{
++ char *c;
++ State *s;
++ char *secret;
++ char decoded[1024];
++ char *entered;
++ int iscli;
++ int digits = 6;
++ int seconds = 30;
++ char *toks[2];
++
++ s = fss->ps;
++ switch (fss->phase) {
++ default:
++ return phaseerror(fss, "write");
++
++ case HaveTotp:
++ iscli = isclient(_strfindattr(s->key->attr, "role"));
++ if (iscli) {
++ c = emalloc(n + 1);
++ memcpy(c, va, n);
++ c[n] = 0;
++
++ if (tokenize(c, toks, 2) != 2) {
++ free(c);
++ return RpcOk;
++ }
++ s->digits = atoi(toks[0]);
++ if (s->digits < 1)
++ s->digits = 6;
++ s->seconds = atoi(toks[1]);
++ if (s->seconds < 1)
++ s->seconds = 30;
++ free(c);
++ fss->phase = HaveDetails;
++ return RpcOk;
++ }
++
++ /* server protocol */
++ c = _strfindattr(s->key->attr, "digits");
++ if (c)
++ digits = atoi(c);
++ c = _strfindattr(s->key->attr, "seconds");
++ if (c)
++ seconds = atoi(c);
++
++ secret = _strfindattr(s->key->privattr, "!secret");
++ dec32((uchar*)decoded, 1024, secret, strlen(secret));
++
++ entered = emalloc(n + 1);
++ memcpy(entered, va, n);
++ entered[n] = 0;
++
++ c = malloc(digits + 1);
++ s->valid = 0;
++
++ if (checkvalid(fss, s, c, time(nil), decoded, seconds, digits, entered))
++ return RpcOk;
++
++ if (checkvalid(fss, s, c, time(nil) - seconds, decoded, seconds, digits, entered))
++ return RpcOk;
++
++ if (checkvalid(fss, s, c, time(nil) + seconds, decoded, seconds, digits, entered))
++ return RpcOk;
++
++ free(c);
++ fss->phase = InvalidOtp;
++ return RpcOk;
++ }
++}
++
++Proto totp =
++{
++.name = "totp",
++.init = totpinit,
++.write = totpwrite,
++.read = totpread,
++.close = totpclose,
++.addkey = replacekey,
++.keyprompt = "user? !secret?",
++};
++
++
++uint
++dohotp(char *key, uvlong counter)
++{
++ uchar hash[SHA1dlen];
++ uchar data[8];
++ data[0] = (counter>>56) & 0xff;
++ data[1] = (counter>>48) & 0xff;
++ data[2] = (counter>>40) & 0xff;
++ data[3] = (counter>>32) & 0xff;
++ data[4] = (counter>>24) & 0xff;
++ data[5] = (counter>>16) & 0xff;
++ data[6] = (counter>>8) & 0xff;
++ data[7] = counter & 0xff;
++ hmac_sha1(data, 8*sizeof(uchar), (uchar*)key, strlen(key), hash, nil);
++
++ int offset = hash[SHA1dlen - 1] & 0x0F;
++ uint result = (hash[offset] & 0x7F) << 24
++ | (hash[offset + 1] & 0xFF) << 16
++ | (hash[offset + 2] & 0xFF) << 8
++ | hash[offset + 3] & 0xFF;
++ uint _hotp = result % (uint)pow10(6);
++
++ return _hotp;
++}
++
++uint
++dototp(char *key, long time, int valid)
++{
++ int number = time/(valid <= 0 ? 30 : valid);
++
++ return dohotp(key, number);
++}