/* * p9cr - one-sided challenge/response authentication * * Protocol: * * C -> S: user * S -> C: challenge * C -> S: response * S -> C: ok or bad * * Note that this is the protocol between factotum and the local * program, not between the two factotums. The information * exchanged here is wrapped in other protocols by the local * programs. */ #include "std.h" #include "dat.h" /* shared with auth dialing routines */ typedef struct ServerState ServerState; struct ServerState { int asfd; Key *k; Ticketreq tr; Ticket t; char *dom; char *hostid; }; enum { MAXCHAL = 64, MAXRESP = 64, }; extern Proto p9cr, vnc; static int p9response(Key*, char*, uchar*, uchar*); static int vncresponse(Key*, char*, uchar*, uchar*); static int p9crchal(ServerState *s, int, char*, uchar*, int); static int p9crresp(ServerState*, uchar*, int); static int p9crcheck(Key *k) { if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){ werrstr("need user and !password attributes"); return -1; } return 0; } static int p9crclient(Conv *c) { char *pw, *res, *user; int challen, resplen, ntry, ret; Attr *attr; Key *k; uchar chal[MAXCHAL+1], resp[MAXRESP]; int (*response)(Key *, char*, uchar*, uchar*); k = nil; res = nil; ret = -1; attr = c->attr; if(c->proto == &p9cr){ challen = NETCHLEN; response = p9response; attr = _mkattr(AttrNameval, "proto", "p9sk1", _delattr(_copyattr(attr), "proto")); }else if(c->proto == &vnc){ challen = MAXCHAL; response = vncresponse; }else{ werrstr("bad proto"); goto out; } c->state = "find key"; k = keyfetch(c, "%A %s", attr, c->proto->keyprompt); if(k == nil) goto out; for(ntry=1;; ntry++){ if(c->attr != attr) freeattr(c->attr); c->attr = addattrs(copyattr(attr), k->attr); if((pw = strfindattr(k->privattr, "!password")) == nil){ werrstr("key has no !password (cannot happen)"); goto out; } if((user = strfindattr(k->attr, "user")) == nil){ werrstr("key has no user (cannot happen)"); goto out; } if(convprint(c, "%s", user) < 0) goto out; if(convread(c, chal, challen) < 0) goto out; chal[challen] = 0; if((resplen = (*response)(k, pw, chal, resp)) < 0) goto out; if(convwrite(c, resp, resplen) < 0) goto out; if(convreadm(c, &res) < 0) goto out; if(strcmp(res, "ok") == 0) break; if((k = keyreplace(c, k, "%s", res)) == nil){ c->state = "auth failed"; werrstr("%s", res); goto out; } } werrstr("succeeded"); ret = 0; out: keyclose(k); if(c->attr != attr) freeattr(attr); return ret; } static int p9crserver(Conv *c) { uchar chal[MAXCHAL], *resp, *resp1; char *user; ServerState s; int astype, ret, challen, resplen; Attr *a; ret = -1; /* user = nil; */ resp = nil; memset(&s, 0, sizeof s); s.asfd = -1; if(c->proto == &p9cr){ astype = AuthChal; challen = NETCHLEN; }else if(c->proto == &vnc){ astype = AuthVNC; challen = MAXCHAL; }else{ werrstr("bad proto"); goto out; } c->state = "find key"; if((s.k = plan9authkey(c->attr)) == nil) goto out; a = copyattr(s.k->attr); a = delattr(a, "proto"); a = delattr(a, "user"); c->attr = addattrs(c->attr, a); freeattr(a); c->state = "authdial"; s.hostid = strfindattr(s.k->attr, "user"); s.dom = strfindattr(s.k->attr, "dom"); if((s.asfd = xioauthdial(nil, s.dom)) < 0){ werrstr("authdial %s: %r", s.dom); goto out; } for(;;){ c->state = "read user"; /* if(convreadm(c, &user) < 0) goto out; */ if((user = strfindattr(c->attr, "user")) == nil) goto out; c->state = "authchal"; if(p9crchal(&s, astype, user, chal, challen) < 0) goto out; c->state = "write challenge"; if(convwrite(c, chal, challen) < 0) goto out; c->state = "read response"; if((resplen = convreadm(c, (char**)(void*)&resp)) < 0) goto out; if(c->proto == &p9cr){ if(resplen > NETCHLEN){ convprint(c, "bad response too long"); goto out; } resp1 = emalloc(NETCHLEN); memset(resp1, 0, NETCHLEN); memmove(resp1, resp, resplen); free(resp); resp = resp1; resplen = NETCHLEN; } c->state = "authwrite"; switch(p9crresp(&s, resp, resplen)){ case -1: fprint(2, "factotum: p9crresp: %r\n"); goto out; case 0: c->state = "write status"; if(convprint(c, "bad authentication failed %r") < 0) goto out; break; case 1: c->state = "write status"; /* if(convprint(c, "ok") < 0) goto out; */ c->done = 1; c->active = 0; if(convprint(c, "haveai") < 0) goto out; goto ok; } /* free(user); */ free(resp); resp = nil; } ok: ret = 0; c->attr = addcap(c->attr, c->sysuser, &s.t); out: keyclose(s.k); /* free(user); */ free(resp); xioclose(s.asfd); return ret; } static int p9crchal(ServerState *s, int astype, char *user, uchar *chal, int challen) { char trbuf[TICKREQLEN]; Ticketreq tr; int n; memset(&tr, 0, sizeof tr); tr.type = astype; if(strlen(s->hostid) >= sizeof tr.hostid){ werrstr("hostid too long"); return -1; } strcpy(tr.hostid, s->hostid); if(strlen(s->dom) >= sizeof tr.authdom){ werrstr("domain too long"); return -1; } strcpy(tr.authdom, s->dom); if(strlen(user) >= sizeof tr.uid){ werrstr("user name too long"); return -1; } strcpy(tr.uid, user); convTR2M(&tr, trbuf); if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN) return -1; if((n=xioasrdresp(s->asfd, chal, challen)) <= 0) return -1; return n; } static int p9crresp(ServerState *s, uchar *resp, int resplen) { char tabuf[TICKETLEN+AUTHENTLEN]; int n; Authenticator a; Ticket t; if(xiowrite(s->asfd, resp, resplen) != resplen) return -1; n = xioasrdresp(s->asfd, tabuf, TICKETLEN+AUTHENTLEN); if(n != TICKETLEN+AUTHENTLEN){ werrstr("short ticket %d; want %d\n", n, TICKETLEN+AUTHENTLEN); return 0; } convM2T(tabuf, &t, s->k->priv); if(t.num != AuthTs || memcmp(t.chal, s->tr.chal, sizeof t.chal) != 0){ werrstr("key mismatch with auth server"); return -1; } convM2A(tabuf+TICKETLEN, &a, t.key); if(a.num != AuthAc || memcmp(a.chal, s->tr.chal, sizeof a.chal) != 0 || a.id != 0){ werrstr("key2 mismatch with auth server"); return -1; } s->t = t; return 1; } static int p9response(Key*, char *pw, uchar *chal, uchar *resp) { char key[DESKEYLEN]; uchar buf[8]; ulong x; passtokey(key, pw); memset(buf, 0, 8); snprint((char*)buf, sizeof buf, "%d", atoi((char*)chal)); if(encrypt(key, buf, 8) < 0){ werrstr("can't encrypt response"); return -1; } x = (buf[0]<<24)+(buf[1]<<16)+(buf[2]<<8)+buf[3]; return snprint((char*)resp, MAXRESP, "%.8lux", x); } static uchar tab[256]; /* VNC reverses the bits of each byte before using as a des key */ static void mktab(void) { int i, j, k; static int once; if(once) return; once = 1; for(i=0; i<256; i++) { j=i; tab[i] = 0; for(k=0; k<8; k++) { tab[i] = (tab[i]<<1) | (j&1); j >>= 1; } } } static int vncresponse(Key *k, char */*pw*/, uchar *chal, uchar *resp) { DESstate des; memmove(resp, chal, MAXCHAL); setupDESstate(&des, k->priv, nil); desECBencrypt(resp, MAXCHAL, &des); return MAXCHAL; } static int vnccheck(Key *k) { uchar *p; char *s; if(!strfindattr(k->attr, "user") || (s = strfindattr(k->privattr, "!password")) == nil){ werrstr("need user and !password attributes"); return -1; } if(k->priv == nil){ mktab(); k->priv = emalloc(8+1); memset(k->priv, 0, 8+1); strncpy((char*)k->priv, s, 8); for(p=k->priv; *p; p++) *p = tab[*p]; } return 0; } static void vncclose(Key *k) { free(k->priv); k->priv = nil; } static Role p9crroles[] = { "client", p9crclient, "server", p9crserver, 0 }; Proto p9cr = { "p9cr", p9crroles, "user? !password?", p9crcheck, nil }; /* still need to implement vnc key generator */ Proto vnc = { "vnc", p9crroles, "user? !password?", vnccheck, vncclose, };