git: plan9front

Download patch

ref: 21731dd45eced03fe5dde6b7421c778f97a09555
parent: 1a919327313d87ca7797928b3cbad80cb40d5167
author: Ori Bernstein <ori@eigenstate.org>
date: Tue Dec 24 21:18:43 EST 2024

auth/factotum: add support for TOTP code generation

--- a/sys/man/4/factotum
+++ b/sys/man/4/factotum
@@ -1,6 +1,6 @@
 .TH FACTOTUM 4
 .SH NAME
-factotum, fgui, userpasswd \- authentication agent
+factotum, fgui, userpasswd, totp \- authentication agent
 .SH SYNOPSIS
 .B auth/factotum
 [
@@ -26,6 +26,14 @@
 .PP
 .B auth/userpasswd
 .I fmt
+.PP
+.B auth/totp
+[
+.B -k
+.I pattern
+] | [
+.B label
+]
 .SH DESCRIPTION
 .I Factotum
 is a user-level file system that
@@ -258,6 +266,15 @@
 .IR fmt .
 This can be used by shell scripts to do cleartext password
 authentication.
+.PP
+.I Totp
+queries and prints an
+.B RFC 6238
+ TOTP code
+for the
+.B proto=totp
+key tuple specified.
+This can be used to authenticate with services that require time based OTP.
 .SS "Key Tuples
 .PP
 A
--- 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 */
--- 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/totp.c
@@ -1,0 +1,146 @@
+#include "dat.h"
+
+typedef struct State State;
+struct State {
+	Key *key;
+};
+
+enum {
+	HaveTotp,
+	Maxphase,
+};
+
+enum {
+	Maxdigits = 8,
+	Sec = 1000*1000*1000,
+};
+
+static char *phasenames[Maxphase] ={
+	[HaveTotp]	"HaveTotp",
+};
+
+static int
+genhotp(uchar *key, int n, uvlong c, int len)
+{
+	uchar hash[SHA1dlen];
+	uchar data[8];
+	u32int h, m;
+	int o;
+
+	data[0] = (c>>56) & 0xff;
+	data[1] = (c>>48) & 0xff;
+	data[2] = (c>>40) & 0xff;
+	data[3] = (c>>32) & 0xff;
+	data[4] = (c>>24) & 0xff;
+	data[5] = (c>>16) & 0xff;
+	data[6] = (c>> 8) & 0xff;
+	data[7] = (c>> 0) & 0xff;
+	hmac_sha1(data, sizeof(data), key, n, hash, nil);
+	
+	o = hash[SHA1dlen - 1] & 0x0F;
+	h = ((hash[o] & 0x7F) << 24)
+		| (hash[o + 1] & 0xFF) << 16
+		| (hash[o + 2] & 0xFF) << 8
+		| hash[o + 3] & 0xFF;
+	m = 1;
+	while(len-- > 0)
+		m *= 10;
+	return h % m;
+}
+
+static int
+gentotp(char *secret, vlong t, int len, vlong period)
+{
+	uchar key[512];
+	int n;
+
+	n = dec32(key, sizeof(key), secret, strlen(secret));
+	if(n < 0){
+		werrstr("invalid totp secret");
+		return -1;
+	}
+	return genhotp(key, n, t/period, len);
+}
+
+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)
+{
+	char *secret, *digits, *period;
+	int len, otp;
+	vlong tdiv;
+	State *s;
+
+	s = fss->ps;
+	len = 6;
+	tdiv = 30ULL*Sec;
+	switch(fss->phase){
+	default:
+		return phaseerror(fss, "read");
+
+	case HaveTotp:
+		digits = _strfindattr(s->key->attr, "digits");
+		secret = _strfindattr(s->key->privattr, "!secret");
+		period = _strfindattr(s->key->attr, "period");
+		if(secret==nil)
+			return failure(fss, "missing totp secret");
+		if(digits != nil)
+			len = atoi(digits);
+		if(period != nil)
+			tdiv = strtoll(period, nil, 0)*Sec;
+		if(*n < len)
+			return toosmall(fss, len);
+		if(len < 1 || len > Maxdigits || tdiv <= 0)
+			return failure(fss, "too many digits");
+		otp = gentotp(secret, nsec(), len, tdiv);
+		if(otp < 0)
+			return failure(fss, "%r");
+		*n = snprint(va, *n, "%.*d", len, otp);
+		return RpcOk;
+	}
+}
+
+static int
+totpwrite(Fsstate *fss, void*, uint)
+{
+	return phaseerror(fss, "write");
+}
+
+Proto totp = {
+	.name=		"totp",
+	.init=		totpinit,
+	.write=		totpwrite,
+	.read=		totpread,
+	.close=		totpclose,
+	.addkey=	replacekey,
+	.keyprompt=	"label? !secret?",
+};
--- a/sys/src/cmd/auth/mkfile
+++ b/sys/src/cmd/auth/mkfile
@@ -35,6 +35,7 @@
 	rsafill\
 	rsagen\
 	ssh2rsa\
+	totp\
 	uniq\
 	userpasswd\
 	warning\
--- /dev/null
+++ b/sys/src/cmd/auth/totp.c
@@ -1,0 +1,48 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+
+char *keypat;
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s fmt\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	char params[512];
+	AuthRpc *rpc;
+	int fd;
+
+	ARGBEGIN{
+	case 'k':
+		keypat = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	quotefmtinstall();
+	if(keypat == nil)
+		snprint(params, sizeof(params), "proto=totp label=%q", argv[0]);
+	else
+		snprint(params, sizeof(params), "proto=totp %s", keypat);
+
+	if((fd = open("/mnt/factotum/rpc", ORDWR|OCEXEC)) == -1)
+		sysfatal("open /mnt/factotum/rpc: %r");
+	if((rpc = auth_allocrpc(fd)) == nil)
+		sysfatal("allocrpc: %r");
+	if(auth_rpc(rpc, "start", params, strlen(params)) != ARok
+	|| auth_rpc(rpc, "read", nil, 0) != ARok)
+		sysfatal("totp proto: %r");
+	rpc->arg[rpc->narg] = '\0';
+	print("%s\n", rpc->arg);
+
+	close(fd);
+	auth_freerpc(rpc);
+	exits(nil);
+}
--