| /* |
| * APOP, CRAM - MD5 challenge/response authentication |
| * |
| * The client does not authenticate the server, hence no CAI. |
| * |
| * Protocol: |
| * |
| * S -> C: random@domain |
| * C -> S: user hex-response |
| * S -> C: ok |
| * |
| * Note that this is the protocol between factotum and the local |
| * program, not between the two factotums. The information |
| * exchanged here is wrapped in the APOP protocol by the local |
| * programs. |
| * |
| * If S sends "bad [msg]" instead of "ok", that is a hint that the key is bad. |
| * The protocol goes back to "C -> S: user hex-response". |
| */ |
| |
| #include "std.h" |
| #include "dat.h" |
| |
| extern Proto apop, cram; |
| |
| static int |
| apopcheck(Key *k) |
| { |
| if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){ |
| werrstr("need user and !password attributes"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int |
| apopclient(Conv *c) |
| { |
| char *chal, *pw, *res; |
| 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 == &apop) |
| astype = AuthApop; |
| else if(c->proto == &cram) |
| astype = AuthCram; |
| else{ |
| werrstr("bad proto"); |
| goto out; |
| } |
| |
| c->state = "find key"; |
| k = keyfetch(c, "%A %s", attr, c->proto->keyprompt); |
| if(k == nil) |
| goto out; |
| |
| c->state = "read challenge"; |
| if((nchal = convreadm(c, &chal)) < 0) |
| 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); |
| |
| switch(astype){ |
| case AuthApop: |
| ds = md5((uchar*)chal, nchal, nil, nil); |
| md5((uchar*)pw, npw, resp, ds); |
| break; |
| case AuthCram: |
| hmac_md5((uchar*)chal, nchal, (uchar*)pw, npw, resp, nil); |
| break; |
| } |
| |
| /* C->S: APOP user hex-response\n */ |
| if(ntry == 1) |
| c->state = "write user"; |
| else{ |
| sprint(c->statebuf, "write user (auth attempt #%d)", ntry); |
| c->state = c->statebuf; |
| } |
| if(convprint(c, "%s", strfindattr(k->attr, "user")) < 0) |
| goto out; |
| |
| c->state = "write response"; |
| if(convprint(c, "%.*H", sizeof resp, resp) < 0) |
| goto out; |
| |
| c->state = "read result"; |
| if(convreadm(c, &res) < 0) |
| goto out; |
| |
| if(strcmp(res, "ok") == 0) |
| break; |
| |
| if(strncmp(res, "bad ", 4) != 0){ |
| werrstr("bad result: %s", res); |
| goto out; |
| } |
| |
| c->state = "replace key"; |
| if((k = keyreplace(c, k, "%s", res+4)) == nil){ |
| c->state = "auth failed"; |
| werrstr("%s", res+4); |
| goto out; |
| } |
| free(res); |
| res = nil; |
| } |
| |
| werrstr("succeeded"); |
| ret = 0; |
| |
| out: |
| keyclose(k); |
| free(chal); |
| if(c->attr != attr) |
| freeattr(attr); |
| return ret; |
| } |
| |
| /* shared with auth dialing routines */ |
| typedef struct ServerState ServerState; |
| struct ServerState |
| { |
| int asfd; |
| Key *k; |
| Ticketreq tr; |
| Ticket t; |
| char *dom; |
| char *hostid; |
| }; |
| |
| enum |
| { |
| APOPCHALLEN = 128 |
| }; |
| |
| static int apopchal(ServerState*, int, char[APOPCHALLEN]); |
| static int apopresp(ServerState*, char*, char*); |
| |
| static int |
| apopserver(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(apopchal(&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; |
| } |
| |
| static int |
| apopchal(ServerState *s, int astype, char chal[APOPCHALLEN]) |
| { |
| char trbuf[TICKREQLEN]; |
| Ticketreq tr; |
| |
| 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); |
| |
| convTR2M(&tr, trbuf); |
| if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN) |
| return -1; |
| |
| if(xioasrdresp(s->asfd, chal, APOPCHALLEN) <= 5) |
| return -1; |
| |
| s->tr = tr; |
| return 0; |
| } |
| |
| static int |
| apopresp(ServerState *s, char *user, char *resp) |
| { |
| char tabuf[TICKETLEN+AUTHENTLEN]; |
| char trbuf[TICKREQLEN]; |
| int len; |
| Authenticator a; |
| Ticket t; |
| Ticketreq tr; |
| |
| tr = s->tr; |
| if(memrandom(tr.chal, CHALLEN) < 0) |
| return -1; |
| |
| if(strlen(user) >= sizeof tr.uid){ |
| werrstr("uid too long"); |
| return -1; |
| } |
| strcpy(tr.uid, user); |
| |
| convTR2M(&tr, trbuf); |
| if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN) |
| return -1; |
| |
| len = strlen(resp); |
| if(xiowrite(s->asfd, resp, len) != len) |
| return -1; |
| |
| if(xioasrdresp(s->asfd, tabuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN) |
| return 0; |
| |
| convM2T(tabuf, &t, s->k->priv); |
| if(t.num != AuthTs |
| || memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){ |
| werrstr("key mismatch with auth server"); |
| return -1; |
| } |
| |
| convM2A(tabuf+TICKETLEN, &a, t.key); |
| if(a.num != AuthAc |
| || memcmp(a.chal, tr.chal, sizeof a.chal) != 0 |
| || a.id != 0){ |
| werrstr("key2 mismatch with auth server"); |
| return -1; |
| } |
| |
| s->t = t; |
| return 1; |
| } |
| |
| static Role |
| apoproles[] = |
| { |
| "client", apopclient, |
| "server", apopserver, |
| 0 |
| }; |
| |
| Proto apop = { |
| "apop", |
| apoproles, |
| "user? !password?", |
| apopcheck, |
| nil |
| }; |
| |
| Proto cram = { |
| "cram", |
| apoproles, |
| "user? !password?", |
| apopcheck, |
| nil |
| }; |
| |