* Re: [9front] Totp in factotum
2023-03-16 19:08 [9front] Totp in factotum (advice and code) sirjofri
@ 2023-03-16 19:51 ` sirjofri
2023-03-17 9:48 ` cinap_lenrek
2023-03-21 20:10 ` [9front] Re: Totp in factotum (advice and code) sirjofri
1 sibling, 1 reply; 7+ messages in thread
From: sirjofri @ 2023-03-16 19:51 UTC (permalink / raw)
To: 9front
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);
+}
^ permalink raw reply [flat|nested] 7+ messages in thread
* [9front] Re: Totp in factotum (advice and code)
2023-03-16 19:08 [9front] Totp in factotum (advice and code) sirjofri
2023-03-16 19:51 ` [9front] Totp in factotum sirjofri
@ 2023-03-21 20:10 ` sirjofri
2023-04-05 7:08 ` sirjofri
1 sibling, 1 reply; 7+ messages in thread
From: sirjofri @ 2023-03-21 20:10 UTC (permalink / raw)
To: 9front
[-- Attachment #1: Type: text/plain, Size: 188 bytes --]
Hey all,
here's a new version which addresses the mentioned issues:
* Now support for binary secrets
* Many bug fixes
* Proper inclusion of test
This time attached as a file.
sirjofri
[-- Attachment #2.1: Type: text/plain, Size: 340 bytes --]
from postmaster@oat:
The following attachment had content that we can't
prove to be harmless. To avoid possible automatic
execution, we changed the content headers.
The original header was:
Content-Type: text/x-diff; charset=us-ascii; name=totp.patch
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename=totp.patch
[-- Attachment #2.2: totp.patch.suspect --]
[-- Type: application/octet-stream, Size: 11917 bytes --]
diff 75337cba3a21530c7da1efaa1ab3b6ed145c5172 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/mkfile
@@ -1,0 +1,10 @@
+</$objtype/mkfile
+
+TEST=\
+ totp\
+
+</sys/src/cmd/mktest
+
+totp.test:V: $O.totptest
+ ../$O.factotum -n
+ $O.totptest
binary files /tmp/diff100000001531 b/sys/src/cmd/auth/factotum/test/totptest.6 differ
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/test/totptest.c
@@ -1,0 +1,232 @@
+#include <u.h>
+#include <libc.h>
+#include <libsec.h>
+#include <auth.h>
+
+void
+fillfactotum(void)
+{
+ int fd;
+ uchar secret[1024];
+ char encoded[1024];
+ char *s;
+ long len, n = 256;
+
+ fd = open("/mnt/factotum/ctl", OWRITE);
+ if (fd < 0)
+ sysfatal("err: %r");
+
+ srand(time(nil));
+ genrandom(secret, n);
+
+ len = enc32(encoded, 1024, secret, n);
+ if (len < 0)
+ sysfatal("err: %r");
+
+ print("secret base32 (%ld encoded, %ld original): %s\n", len, n, encoded);
+
+ // has to use write instead of fprint since fprint uses a 256 buffer
+
+ s = smprint("key proto=totp user=a role=client !secret=%s\n", encoded);
+ len = write(fd, s, strlen(s));
+ if (len < 0)
+ fprint(2, "err client a: %r\n");
+ free(s);
+ s = smprint("key proto=totp user=a !secret=%s role=server\n", encoded);
+ len = write(fd, s, strlen(s));
+ if (len < 0)
+ fprint(2, "err server a: %r\n");
+ free(s);
+ s = smprint("key proto=totp user=b role=client !secret=%s\n", encoded);
+ len = write(fd, s, strlen(s));
+ if (len < 0)
+ fprint(2, "err client b: %r\n");
+ free(s);
+ s = smprint("key proto=totp user=b role=server digits=9 seconds=10 !secret=%s\n", encoded);
+ len = write(fd, s, strlen(s));
+ if (len < 0)
+ fprint(2, "err server b: %r\n");
+ free(s);
+
+ close(fd);
+}
+
+void
+main(void)
+{
+ int fd;
+ AuthRpc *rpc;
+ int n;
+ uint r;
+ char *s;
+ char response[8192];
+ char *toks[2];
+ char *otp;
+ char *invalid = "000000";
+
+ fillfactotum();
+
+ /******/
+ /* generate OTP */
+ print("\n Testing client get OTP\n");
+
+ 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("success: client fetch OTP\n\n");
+ free(s);
+
+ /********/
+ /* valid OTP test */
+ print("\n Testing server check OTP\n");
+
+ 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=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("failed: valid otp: %s\n\n", rpc->arg);
+ else
+ print("success: valid otp: %s\n\n", rpc->arg);
+
+ auth_freerpc(rpc);
+ close(fd);
+
+ /*******/
+ /* invalid OTP test */
+ print("\n Testing server check invalid OTP\n");
+
+ 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("success: invalid otp: %s\n\n", rpc->arg);
+ else
+ print("failed: invalid otp: %s\n\n", rpc->arg);
+
+ auth_freerpc(rpc);
+ close(fd);
+
+ /******/
+ /* generate OTP with digits */
+ print("\n Testing client get OTP with custom digits/seconds\n");
+
+ 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("success: get client custom\n\n");
+ free(s);
+
+ /********/
+ /* valid OTP test with digits and seconds (custom) */
+ print("\n Testing server check OTP with custom digits/seconds\n");
+
+ 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=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("failed: valid otp custom: %s\n\n", rpc->arg);
+ else
+ print("success: valid otp custom: %s\n\n", rpc->arg);
+
+ auth_freerpc(rpc);
+ close(fd);
+
+ exits(nil);
+}
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/totp.c
@@ -1,0 +1,296 @@
+/*
+ * 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(uchar *key, ulong keylen, uvlong counter, int digits);
+int dototp(char *key, long time, int valid, int digits, uint *otp);
+
+char *validstr = "valid";
+int validstrlen = -1;
+
+typedef struct State State;
+struct State
+{
+ Key *key;
+ int valid;
+ int seconds;
+ int digits;
+ char *secret;
+};
+
+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;
+ //fss->maxphase = Maxphase;
+ 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;
+ 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");
+ if (!secret)
+ return failure(fss, "no secret found");
+
+ t = time(nil);
+ if (dototp(secret, t, s->seconds, s->digits, &otp) < 0)
+ return failure(fss, "can't decode secret");
+
+ 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 *secret, char *entered)
+{
+ uint otp;
+
+ if (dototp(secret, t, s->seconds, s->digits, &otp) < 0) {
+ s->valid = 0;
+ fss->phase = InvalidOtp;
+ return 0;
+ }
+
+ snprint(c, s->digits + 1, "%0*d", s->digits, otp);
+ if (strcmp(c, entered) == 0) {
+ free(c);
+ free(entered);
+ 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 *entered;
+ ulong t;
+ int iscli;
+ 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 */
+ s->digits = 6;
+ s->seconds = 30;
+ c = _strfindattr(s->key->attr, "digits");
+ if (c)
+ s->digits = atoi(c);
+ if (s->digits < 1)
+ s->digits = 1;
+
+ c = _strfindattr(s->key->attr, "seconds");
+ if (c)
+ s->seconds = atoi(c);
+ if (s->seconds < 1)
+ s->seconds = 1;
+
+ secret = _strfindattr(s->key->privattr, "!secret");
+ if (!secret)
+ return failure(fss, "no secret found");
+
+ entered = emalloc(n + 1);
+ memcpy(entered, va, n);
+ entered[n] = 0;
+
+ c = malloc(s->digits + 1);
+ s->valid = 0;
+ t = time(nil);
+
+ if (checkvalid(fss, s, c, t, secret, entered))
+ return RpcOk;
+
+ if (checkvalid(fss, s, c, t - s->seconds, secret, entered))
+ return RpcOk;
+
+ if (checkvalid(fss, s, c, t + s->seconds, secret, entered))
+ return RpcOk;
+
+ free(entered);
+ 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(uchar *key, ulong keylen, uvlong counter, int digits)
+{
+ 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, sizeof(data), key, keylen, 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(digits);
+
+ return _hotp;
+}
+
+int
+dototp(char *key, long time, int valid, int digits, uint *otp)
+{
+ uchar decoded[1024];
+ long len;
+ int number;
+
+ len = dec32(decoded, 1024, key, strlen(key));
+ if (len < 0)
+ return -1;
+
+ number = time/valid;
+
+ *otp = dohotp(decoded, (ulong)len, number, digits);
+ return 1;
+}
^ permalink raw reply [flat|nested] 7+ messages in thread