9front - general discussion about 9front
 help / color / mirror / Atom feed
From: sirjofri <sirjofri+ml-9front@sirjofri.de>
To: 9front@9front.org
Subject: Re: [9front] Totp in factotum
Date: Thu, 16 Mar 2023 20:51:48 +0100 (GMT+01:00)	[thread overview]
Message-ID: <4da1abf2-25e8-471d-9ecf-bc09ea182933@sirjofri.de> (raw)
In-Reply-To: <3a87efed-5286-45f6-b3f1-1874f0e87a55@sirjofri.de>

I don't want to clutter this thread, but moody is right, so here's a proper patch (exactly the same content as before).

sirjofri

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);
+}

  reply	other threads:[~2023-03-16 19:53 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-03-16 19:08 [9front] Totp in factotum (advice and code) sirjofri
2023-03-16 19:51 ` sirjofri [this message]
2023-03-17  9:48   ` [9front] Totp in factotum cinap_lenrek
2023-03-17 16:43     ` sirjofri
2023-03-18  1:42       ` cinap_lenrek
2023-03-21 20:10 ` [9front] Re: Totp in factotum (advice and code) sirjofri
2023-04-05  7:08   ` sirjofri

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4da1abf2-25e8-471d-9ecf-bc09ea182933@sirjofri.de \
    --to=sirjofri+ml-9front@sirjofri.de \
    --cc=9front@9front.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).