/* * 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; tr->type = AuthTreq; 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, "!hex")){ if(hexparse(pass, k->priv, 7) < 0){ werrstr("malformed !hex key data"); return -1; } }else if(pass = strfindattr(k->privattr, "!password")) passtokey(k->priv, pass); 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 };