/* * 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)){ default: case -1: goto out; case 0: c->state = "write status"; if(convprint(c, "bad authentication failed") < 0) goto out; break; case 1: c->done = 1; c->active = 0; 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(len != 2*MD5dlen){ werrstr("response not MD5 digest"); return -1; } 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 };