| /* |
| * 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, |
| }; |