shithub: totp

Download patch

ref: 24bc7678e8c6988da430446e59240f722dbc8043
parent: 8e3d8120a945bad722e218963699f0a17a853d86
author: sirjofri <[email protected]>
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);
++}