/* * p9cr, vnc - 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" static int p9crclient(Conv *c) { char *chal, *pw, *res, *user; int astype, nchal, npw, ntry, ret; uchar resp[MD5dlen]; Attr *attr; DigestState *ds; Key *k; chal = nil; k = nil; res = nil; ret = -1; attr = c->attr; 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"; 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; } npw = strlen(pw); 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(convreadm(c, &chal) < 0) goto out; if((nresp = (*response)(chal, resp)) < 0) goto out; if(convwrite(c, resp, nresp) < 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); free(chal); if(c->attr != attr) freeattr(attr); return ret; } static int p9crserver(Conv *c) { char chal[MAXCHAL], *user, *resp; int astype, challen, asfd, fd, ret; Attr *a; Key *k; char *hostid, *dom; 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((k = plan9authkey(c->attr)) == nil) goto out; /* a = copyattr(k->attr); a = delattr(a, "proto"); c->attr = addattrs(c->attr, a); freeattr(a); */ c->state = "authdial"; hostid = strfindattr(s.k->attr, "user"); dom = strfindattr(s.k->attr, "dom"); if((asfd = xioauthdial(nil, s.dom)) < 0){ werrstr("authdial %s: %r", s.dom); goto out; } c->state = "authchal"; if(p9crchal(&s, astype, chal) < 0) goto out; c->state = "write challenge"; if(convprint(c, "%s", chal) < 0) goto out; for(;;){ c->state = "read user"; if(convreadm(c, &user) < 0) goto out; c->state = "read response"; if(convreadm(c, &resp) < 0) goto out; c->state = "authwrite"; switch(apopresp(&s, user, resp)){ case -1: goto out; case 0: c->state = "write status"; if(convprint(c, "bad authentication failed") < 0) goto out; break; case 1: c->state = "write status"; if(convprint(c, "ok") < 0) goto out; goto ok; } free(user); free(resp); user = nil; 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; } enum { MAXCHAL = 64, }; typedef struct State State; struct State { Key *key; int astype; int asfd; Ticket t; Ticketreq tr; char chal[MAXCHAL]; int challen; char resp[MAXCHAL]; int resplen; }; enum { CNeedChal, CHaveResp, SHaveChal, SNeedResp, Maxphase, }; static char *phasenames[Maxphase] = { [CNeedChal] "CNeedChal", [CHaveResp] "CHaveResp", [SHaveChal] "SHaveChal", [SNeedResp] "SNeedResp", }; static void p9crclose(Fsstate *fss) { State *s; s = fss->ps; if(s->asfd >= 0){ close(s->asfd); s->asfd = -1; } free(s); } static int getchal(State*, Fsstate*); static int p9crinit(Proto *p, Fsstate *fss) { int iscli, ret; char *user; State *s; Attr *attr; if((iscli = isclient(_str_findattr(fss->attr, "role"))) < 0) return failure(fss, nil); s = emalloc(sizeof(*s)); s->asfd = -1; if(p == &p9cr){ s->astype = AuthChal; s->challen = NETCHLEN; }else if(p == &vnc){ s->astype = AuthVNC; s->challen = Maxchal; }else abort(); if(iscli){ fss->phase = CNeedChal; if(p == &p9cr) attr = setattr(_copyattr(fss->attr), "proto=p9sk1"); else attr = nil; ret = findkey(&s->key, fss, Kuser, 0, attr ? attr : fss->attr, "role=client %s", p->keyprompt); _freeattr(attr); if(ret != RpcOk){ free(s); return ret; } fss->ps = s; }else{ if((ret = findp9authkey(&s->key, fss)) != RpcOk){ free(s); return ret; } if((user = _str_findattr(fss->attr, "user")) == nil){ free(s); return failure(fss, "no user name specified in start msg"); } if(strlen(user) >= sizeof s->tr.uid){ free(s); return failure(fss, "user name too long"); } fss->ps = s; strcpy(s->tr.uid, user); ret = getchal(s, fss); if(ret != RpcOk){ p9crclose(fss); /* frees s */ fss->ps = nil; } } fss->phasename = phasenames; fss->maxphase = Maxphase; return ret; } static int p9crread(Fsstate *fss, void *va, uint *n) { int m; State *s; s = fss->ps; switch(fss->phase){ default: return phaseerror(fss, "read"); case CHaveResp: if(s->resplen < *n) *n = s->resplen; memmove(va, s->resp, *n); fss->phase = Established; return RpcOk; case SHaveChal: if(s->astype == AuthChal) m = strlen(s->chal); /* ascii string */ else m = s->challen; /* fixed length binary */ if(m > *n) return toosmall(fss, m); *n = m; memmove(va, s->chal, m); fss->phase = SNeedResp; return RpcOk; } } static int p9response(Fsstate *fss, State *s) { char key[DESKEYLEN]; uchar buf[8]; ulong chal; char *pw; pw = _str_findattr(s->key->privattr, "!password"); if(pw == nil) return failure(fss, "vncresponse cannot happen"); passtokey(key, pw); memset(buf, 0, 8); sprint((char*)buf, "%d", atoi(s->chal)); if(encrypt(key, buf, 8) < 0) return failure(fss, "can't encrypt response"); chal = (buf[0]<<24)+(buf[1]<<16)+(buf[2]<<8)+buf[3]; s->resplen = snprint(s->resp, sizeof s->resp, "%.8lux", chal); return RpcOk; } 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 vncaddkey(Key *k) { uchar *p; char *s; k->priv = emalloc(8+1); if(s = _str_findattr(k->privattr, "!password")){ mktab(); memset(k->priv, 0, 8+1); strncpy((char*)k->priv, s, 8); for(p=k->priv; *p; p++) *p = tab[*p]; }else{ werrstr("no key data"); return -1; } return replacekey(k); } static void vncclosekey(Key *k) { free(k->priv); } static int vncresponse(Fsstate*, State *s) { DESstate des; memmove(s->resp, s->chal, sizeof s->chal); setupDESstate(&des, s->key->priv, nil); desECBencrypt((uchar*)s->resp, s->challen, &des); s->resplen = s->challen; return RpcOk; } static int p9crwrite(Fsstate *fss, void *va, uint n) { char tbuf[TICKETLEN+AUTHENTLEN]; State *s; char *data = va; Authenticator a; char resp[Maxchal]; int ret; s = fss->ps; switch(fss->phase){ default: return phaseerror(fss, "write"); case CNeedChal: if(n >= sizeof(s->chal)) return failure(fss, Ebadarg); memset(s->chal, 0, sizeof s->chal); memmove(s->chal, data, n); s->challen = n; if(s->astype == AuthChal) ret = p9response(fss, s); else ret = vncresponse(fss, s); if(ret != RpcOk) return ret; fss->phase = CHaveResp; return RpcOk; case SNeedResp: /* send response to auth server and get ticket */ if(n > sizeof(resp)) return failure(fss, Ebadarg); memset(resp, 0, sizeof resp); memmove(resp, data, n); if(write(s->asfd, resp, s->challen) != s->challen) return failure(fss, Easproto); /* get ticket plus authenticator from auth server */ if(_asrdresp(s->asfd, tbuf, TICKETLEN+AUTHENTLEN) < 0) return failure(fss, nil); /* check ticket */ convM2T(tbuf, &s->t, s->key->priv); if(s->t.num != AuthTs || memcmp(s->t.chal, s->tr.chal, sizeof(s->t.chal)) != 0) return failure(fss, Easproto); convM2A(tbuf+TICKETLEN, &a, s->t.key); if(a.num != AuthAc || memcmp(a.chal, s->tr.chal, sizeof(a.chal)) != 0 || a.id != 0) return failure(fss, Easproto); fss->haveai = 1; fss->ai.cuid = s->t.cuid; fss->ai.suid = s->t.suid; fss->ai.nsecret = 0; fss->ai.secret = nil; fss->phase = Established; return RpcOk; } } static int getchal(State *s, Fsstate *fss) { char trbuf[TICKREQLEN]; int n; safecpy(s->tr.hostid, _str_findattr(s->key->attr, "user"), sizeof(s->tr.hostid)); safecpy(s->tr.authdom, _str_findattr(s->key->attr, "dom"), sizeof(s->tr.authdom)); s->tr.type = s->astype; convTR2M(&s->tr, trbuf); /* get challenge from auth server */ s->asfd = _authdial(nil, _str_findattr(s->key->attr, "dom")); if(s->asfd < 0) return failure(fss, Easproto); if(write(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN) return failure(fss, Easproto); n = _asrdresp(s->asfd, s->chal, s->challen); if(n <= 0){ if(n == 0) werrstr("_asrdresp short read"); return failure(fss, nil); } s->challen = n; fss->phase = SHaveChal; return RpcOk; } Proto p9cr = { .name= "p9cr", .init= p9crinit, .write= p9crwrite, .read= p9crread, .close= p9crclose, .keyprompt= "user? !password?", }; Proto vnc = { .name= "vnc", .init= p9crinit, .write= p9crwrite, .read= p9crread, .close= p9crclose, .keyprompt= "!password?", .addkey= vncaddkey, };