auth/factotum: add support for TOTP code generation

This commit is contained in:
Ori Bernstein 2024-12-25 02:18:43 +00:00
parent 1a91932731
commit 21731dd45e
7 changed files with 216 additions and 1 deletions

View file

@ -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 @@ factotum, fgui, userpasswd \- authentication agent
.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 @@ key tuple specified in
.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

View file

@ -228,3 +228,4 @@ extern Proto rsa; /* rsa.c */
extern Proto httpdigest; /* httpdigest.c */
extern Proto ecdsa; /* ecdsa.c */
extern Proto wpapsk; /* wpapsk.c */
extern Proto totp; /* totp */

View file

@ -43,6 +43,7 @@ prototab[] =
&vnc,
&ecdsa,
&wpapsk,
&totp,
nil,
};

View file

@ -14,6 +14,7 @@ PROTO=\
rsa.$O\
ecdsa.$O\
wpapsk.$O\
totp.$O\
FOFILES=\
$PROTO\

View file

@ -0,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?",
};

View file

@ -35,6 +35,7 @@ TARG=\
rsafill\
rsagen\
ssh2rsa\
totp\
uniq\
userpasswd\
warning\

48
sys/src/cmd/auth/totp.c Normal file
View file

@ -0,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);
}