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 .TH FACTOTUM 4
.SH NAME .SH NAME
factotum, fgui, userpasswd \- authentication agent factotum, fgui, userpasswd, totp \- authentication agent
.SH SYNOPSIS .SH SYNOPSIS
.B auth/factotum .B auth/factotum
[ [
@ -26,6 +26,14 @@ factotum, fgui, userpasswd \- authentication agent
.PP .PP
.B auth/userpasswd .B auth/userpasswd
.I fmt .I fmt
.PP
.B auth/totp
[
.B -k
.I pattern
] | [
.B label
]
.SH DESCRIPTION .SH DESCRIPTION
.I Factotum .I Factotum
is a user-level file system that is a user-level file system that
@ -258,6 +266,15 @@ key tuple specified in
.IR fmt . .IR fmt .
This can be used by shell scripts to do cleartext password This can be used by shell scripts to do cleartext password
authentication. 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 .SS "Key Tuples
.PP .PP
A A

View file

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

View file

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

View file

@ -14,6 +14,7 @@ PROTO=\
rsa.$O\ rsa.$O\
ecdsa.$O\ ecdsa.$O\
wpapsk.$O\ wpapsk.$O\
totp.$O\
FOFILES=\ FOFILES=\
$PROTO\ $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\ rsafill\
rsagen\ rsagen\
ssh2rsa\ ssh2rsa\
totp\
uniq\ uniq\
userpasswd\ userpasswd\
warning\ 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);
}