blob: 0a79a361bede4c53d0a41afc1f9341652b6c84aa [file] [log] [blame]
/*
* p9sk1, p9sk2 - Plan 9 secret (private) key authentication.
* p9sk2 is an incomplete flawed variant of p9sk1.
*
* Client protocol:
* write challenge[challen] (p9sk1 only)
* read tickreq[tickreqlen]
* write ticket[ticketlen]
* read authenticator[authentlen]
*
* Server protocol:
* read challenge[challen] (p9sk1 only)
* write tickreq[tickreqlen]
* read ticket[ticketlen]
* write authenticator[authentlen]
*/
#include "std.h"
#include "dat.h"
extern Proto p9sk1, p9sk2;
static int gettickets(Ticketreq*, char*, Key*);
#define max(a, b) ((a) > (b) ? (a) : (b))
enum
{
MAXAUTH = max(TICKREQLEN, TICKETLEN+max(TICKETLEN, AUTHENTLEN))
};
static int
p9skclient(Conv *c)
{
char *user;
char cchal[CHALLEN];
uchar secret[8];
char buf[MAXAUTH];
int speakfor, ret;
Attr *a;
Authenticator au;
Key *k;
Ticket t;
Ticketreq tr;
ret = -1;
a = nil;
k = nil;
/* p9sk1: send client challenge */
if(c->proto == &p9sk1){
c->state = "write challenge";
memrandom(cchal, CHALLEN);
if(convwrite(c, cchal, CHALLEN) < 0)
goto out;
}
/* read ticket request */
c->state = "read tickreq";
if(convread(c, buf, TICKREQLEN) < 0)
goto out;
convM2TR(buf, &tr);
/* p9sk2: use server challenge as client challenge */
if(c->proto == &p9sk2)
memmove(cchal, tr.chal, CHALLEN);
/*
* find a key.
*
* if the user is the factotum owner, any key will do.
* if not, then if we have a speakfor key,
* we will only vouch for the user's local identity.
*
* this logic is duplicated in p9any.c
*/
user = strfindattr(c->attr, "user");
a = delattr(copyattr(c->attr), "role");
a = addattr(a, "proto=p9sk1");
if(strcmp(c->sysuser, owner) == 0){
speakfor = 0;
a = addattr(a, "proto=p9sk1 user? dom=%q", tr.authdom);
}else if(user==nil || strcmp(c->sysuser, user)==0){
speakfor = 1;
a = delattr(a, "user");
a = addattr(a, "proto=p9sk1 user? dom=%q role=speakfor", tr.authdom);
}else{
werrstr("will not authenticate for %q as %q", c->sysuser, user);
goto out;
}
for(;;){
c->state = "find key";
k = keyfetch(c, "%A", a);
if(k == nil)
goto out;
/* relay ticket request to auth server, get tickets */
strcpy(tr.hostid, strfindattr(k->attr, "user"));
if(speakfor)
strcpy(tr.uid, c->sysuser);
else
strcpy(tr.uid, tr.hostid);
c->state = "get tickets";
if(gettickets(&tr, buf, k) < 0)
goto out;
convM2T(buf, &t, k->priv);
if(t.num == AuthTc)
break;
/* we don't agree with the auth server about the key; try again */
c->state = "replace key";
if((k = keyreplace(c, k, "key mismatch with auth server")) == nil){
werrstr("key mismatch with auth server");
goto out;
}
}
/* send second ticket and authenticator to server */
c->state = "write ticket+auth";
memmove(buf, buf+TICKETLEN, TICKETLEN);
au.num = AuthAc;
memmove(au.chal, tr.chal, CHALLEN);
au.id = 0;
convA2M(&au, buf+TICKETLEN, t.key);
if(convwrite(c, buf, TICKETLEN+AUTHENTLEN) < 0)
goto out;
/* read authenticator from server */
c->state = "read auth";
if(convread(c, buf, AUTHENTLEN) < 0)
goto out;
convM2A(buf, &au, t.key);
if(au.num != AuthAs || memcmp(au.chal, cchal, CHALLEN) != 0 || au.id != 0){
werrstr("server lies through his teeth");
goto out;
}
/* success */
c->attr = addcap(c->attr, c->sysuser, &t);
flog("p9skclient success %A", c->attr); /* before adding secret! */
des56to64((uchar*)t.key, secret);
c->attr = addattr(c->attr, "secret=%.8H", secret);
ret = 0;
out:
if(ret < 0)
flog("p9skclient: %r");
freeattr(a);
keyclose(k);
return ret;
}
static int
p9skserver(Conv *c)
{
char cchal[CHALLEN], buf[MAXAUTH];
uchar secret[8];
int ret;
Attr *a;
Authenticator au;
Key *k;
Ticketreq tr;
Ticket t;
ret = -1;
a = addattr(copyattr(c->attr), "user? dom?");
a = addattr(a, "user? dom? proto=p9sk1");
if((k = keyfetch(c, "%A", a)) == nil)
goto out;
/* p9sk1: read client challenge */
if(c->proto == &p9sk1){
if(convread(c, cchal, CHALLEN) < 0)
goto out;
}
/* send ticket request */
memset(&tr, 0, sizeof tr);
tr.type = AuthTreq;
strcpy(tr.authid, strfindattr(k->attr, "user"));
strcpy(tr.authdom, strfindattr(k->attr, "dom"));
memrandom(tr.chal, sizeof tr.chal);
convTR2M(&tr, buf);
if(convwrite(c, buf, TICKREQLEN) < 0)
goto out;
/* p9sk2: use server challenge as client challenge */
if(c->proto == &p9sk2)
memmove(cchal, tr.chal, sizeof tr.chal);
/* read ticket+authenticator */
if(convread(c, buf, TICKETLEN+AUTHENTLEN) < 0)
goto out;
convM2T(buf, &t, k->priv);
if(t.num != AuthTs || memcmp(t.chal, tr.chal, CHALLEN) != 0){
/* BUG badkey */
werrstr("key mismatch with auth server");
goto out;
}
convM2A(buf+TICKETLEN, &au, t.key);
if(au.num != AuthAc || memcmp(au.chal, tr.chal, CHALLEN) != 0 || au.id != 0){
werrstr("client lies through his teeth");
goto out;
}
/* send authenticator */
au.num = AuthAs;
memmove(au.chal, cchal, CHALLEN);
convA2M(&au, buf, t.key);
if(convwrite(c, buf, AUTHENTLEN) < 0)
goto out;
/* success */
c->attr = addcap(c->attr, c->sysuser, &t);
flog("p9skserver success %A", c->attr); /* before adding secret! */
des56to64((uchar*)t.key, secret);
c->attr = addattr(c->attr, "secret=%.8H", secret);
ret = 0;
out:
if(ret < 0)
flog("p9skserver: %r");
freeattr(a);
keyclose(k);
return ret;
}
int
_asgetticket(int fd, char *trbuf, char *tbuf)
{
if(write(fd, trbuf, TICKREQLEN) < 0){
close(fd);
return -1;
}
return _asrdresp(fd, tbuf, 2*TICKETLEN);
}
static int
getastickets(Ticketreq *tr, char *buf)
{
int asfd;
int ret;
if((asfd = xioauthdial(nil, tr->authdom)) < 0)
return -1;
convTR2M(tr, buf);
ret = xioasgetticket(asfd, buf, buf);
xioclose(asfd);
return ret;
}
static int
mktickets(Ticketreq *tr, char *buf, Key *k)
{
Ticket t;
if(strcmp(tr->authid, tr->hostid) != 0)
return -1;
memset(&t, 0, sizeof t);
memmove(t.chal, tr->chal, CHALLEN);
strcpy(t.cuid, tr->uid);
strcpy(t.suid, tr->uid);
memrandom(t.key, DESKEYLEN);
t.num = AuthTc;
convT2M(&t, buf, k->priv);
t.num = AuthTs;
convT2M(&t, buf+TICKETLEN, k->priv);
return 0;
}
static int
gettickets(Ticketreq *tr, char *buf, Key *k)
{
if(getastickets(tr, buf) == 0)
return 0;
if(mktickets(tr, buf, k) == 0)
return 0;
werrstr("gettickets: %r");
return -1;
}
static int
p9sk1check(Key *k)
{
char *user, *dom, *pass;
Ticketreq tr;
user = strfindattr(k->attr, "user");
dom = strfindattr(k->attr, "dom");
if(user==nil || dom==nil){
werrstr("need user and dom attributes");
return -1;
}
if(strlen(user) >= sizeof tr.authid){
werrstr("user name too long");
return -1;
}
if(strlen(dom) >= sizeof tr.authdom){
werrstr("auth dom name too long");
return -1;
}
k->priv = emalloc(DESKEYLEN);
if(pass = strfindattr(k->privattr, "!password"))
passtokey(k->priv, pass);
else if(pass = strfindattr(k->privattr, "!hex")){
if(hexparse(pass, k->priv, 7) < 0){
werrstr("malformed !hex key data");
return -1;
}
}else{
werrstr("need !password or !hex attribute");
return -1;
}
return 0;
}
static void
p9sk1close(Key *k)
{
free(k->priv);
k->priv = nil;
}
static Role
p9sk1roles[] =
{
"client", p9skclient,
"server", p9skserver,
0
};
static Role
p9sk2roles[] =
{
"client", p9skclient,
"server", p9skserver,
0
};
Proto p9sk1 = {
"p9sk1",
p9sk1roles,
"user? dom? !password?",
p9sk1check,
p9sk1close
};
Proto p9sk2 = {
"p9sk2",
p9sk2roles
};