ref: d95c83d30d3a9a4d5cba5cc9f803865931333bee
dir: /factotum_totp.diff/
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); +}