diff options
Diffstat (limited to 'src/cmd/auth/factotum/p9cr.c')
-rw-r--r-- | src/cmd/auth/factotum/p9cr.c | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/src/cmd/auth/factotum/p9cr.c b/src/cmd/auth/factotum/p9cr.c new file mode 100644 index 00000000..7f53e447 --- /dev/null +++ b/src/cmd/auth/factotum/p9cr.c @@ -0,0 +1,545 @@ +/* + * 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 +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 *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[APOPCHALLEN], *user, *resp; + ServerState s; + int astype, ret; + Attr *a; + + ret = -1; + user = nil; + resp = nil; + memset(&s, 0, sizeof s); + s.asfd = -1; + + if(c->proto == &apop) + astype = AuthApop; + else if(c->proto == &cram) + astype = AuthCram; + 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"); + 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; + } + + 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, +}; |