diff options
Diffstat (limited to 'src/cmd/auth/factotum/chap.c')
-rw-r--r-- | src/cmd/auth/factotum/chap.c | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/src/cmd/auth/factotum/chap.c b/src/cmd/auth/factotum/chap.c new file mode 100644 index 00000000..2b258902 --- /dev/null +++ b/src/cmd/auth/factotum/chap.c @@ -0,0 +1,426 @@ +/* + * CHAP, MSCHAP + * + * The client does not authenticate the server, hence no CAI + * + * Protocol: + * + * S -> C: random 8-byte challenge + * C -> S: user in UTF-8 + * C -> S: Chapreply or MSchapreply structure + * S -> C: ok or 'bad why' + * + * The chap protocol requires the client to give it id=%d, the id of + * the PPP message containing the challenge, which is used + * as part of the response. Because the client protocol is message-id + * specific, there is no point in looping to try multiple keys. + * + * The MS chap protocol actually uses two different hashes, an + * older insecure one called the LM (Lan Manager) hash, and a newer + * more secure one called the NT hash. By default we send back only + * the NT hash, because the LM hash can help an eavesdropper run + * a brute force attack. If the key has an lm attribute, then we send only the + * LM hash. + */ + +#include "std.h" +#include "dat.h" + +extern Proto chap, mschap; + +enum { + ChapChallen = 8, + + MShashlen = 16, + MSchallen = 8, + MSresplen = 24, +}; + +static int +chapcheck(Key *k) +{ + if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){ + werrstr("need user and !password attributes"); + return -1; + } + return 0; +} + +static void +nthash(uchar hash[MShashlen], char *passwd) +{ + uchar buf[512]; + int i; + + for(i=0; *passwd && i<sizeof(buf); passwd++) { + buf[i++] = *passwd; + buf[i++] = 0; + } + + memset(hash, 0, 16); + + md4(buf, i, hash, 0); +} + +static void +desencrypt(uchar data[8], uchar key[7]) +{ + ulong ekey[32]; + + key_setup(key, ekey); + block_cipher(ekey, data, 0); +} + +static void +lmhash(uchar hash[MShashlen], char *passwd) +{ + uchar buf[14]; + char *stdtext = "KGS!@#$%"; + int i; + + strncpy((char*)buf, passwd, sizeof(buf)); + for(i=0; i<sizeof(buf); i++) + if(buf[i] >= 'a' && buf[i] <= 'z') + buf[i] += 'A' - 'a'; + + memset(hash, 0, 16); + memcpy(hash, stdtext, 8); + memcpy(hash+8, stdtext, 8); + + desencrypt(hash, buf); + desencrypt(hash+8, buf+7); +} + +static void +mschalresp(uchar resp[MSresplen], uchar hash[MShashlen], uchar chal[MSchallen]) +{ + int i; + uchar buf[21]; + + memset(buf, 0, sizeof(buf)); + memcpy(buf, hash, MShashlen); + + for(i=0; i<3; i++) { + memmove(resp+i*MSchallen, chal, MSchallen); + desencrypt(resp+i*MSchallen, buf+i*7); + } +} + +static int +chapclient(Conv *c) +{ + int id, astype, nchal, npw, ret; + uchar *chal; + char *s, *pw, *user, *res; + Attr *attr; + Key *k; + Chapreply cr; + MSchapreply mscr; + DigestState *ds; + + ret = -1; + chal = nil; + k = nil; + attr = c->attr; + + if(c->proto == &chap){ + astype = AuthChap; + s = strfindattr(attr, "id"); + if(s == nil || *s == 0){ + werrstr("need id=n attr in start message"); + goto out; + } + id = strtol(s, &s, 10); + if(*s != 0 || id < 0 || id >= 256){ + werrstr("bad id=n attr in start message"); + goto out; + } + cr.id = id; + }else if(c->proto == &mschap) + astype = AuthMSchap; + 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->attr = addattrs(copyattr(attr), k->attr); + + c->state = "read challenge"; + if((nchal = convreadm(c, (char**)(void*)&chal)) < 0) + goto out; + if(astype == AuthMSchap && nchal != MSchallen) + c->state = "write user"; + if((user = strfindattr(k->attr, "user")) == nil){ + werrstr("key has no user (cannot happen?)"); + goto out; + } + if(convprint(c, "%s", user) < 0) + goto out; + + c->state = "write response"; + if((pw = strfindattr(k->privattr, "!password")) == nil){ + werrstr("key has no password (cannot happen?)"); + goto out; + } + npw = strlen(pw); + + if(astype == AuthChap){ + ds = md5(&cr.id, 1, 0, 0); + md5((uchar*)pw, npw, 0, ds); + md5(chal, nchal, (uchar*)cr.resp, ds); + if(convwrite(c, &cr, sizeof cr) < 0) + goto out; + }else{ + uchar hash[MShashlen]; + + memset(&mscr, 0, sizeof mscr); + if(strfindattr(k->attr, "lm")){ + lmhash(hash, pw); + mschalresp((uchar*)mscr.LMresp, hash, chal); + }else{ + nthash(hash, pw); + mschalresp((uchar*)mscr.NTresp, hash, chal); + } + if(convwrite(c, &mscr, sizeof mscr) < 0) + goto out; + } + + c->state = "read result"; + if(convreadm(c, &res) < 0) + goto out; + if(strcmp(res, "ok") == 0){ + ret = 0; + werrstr("succeeded"); + goto out; + } + if(strncmp(res, "bad ", 4) != 0){ + werrstr("bad result: %s", res); + goto out; + } + + c->state = "replace key"; + keyevict(c, k, "%s", res+4); + werrstr("%s", res+4); + +out: + free(res); + 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; +}; + +static int chapchal(ServerState*, int, char[ChapChallen]); +static int chapresp(ServerState*, char*, char*); + +static int +chapserver(Conv *c) +{ + char chal[ChapChallen], *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 == &chap) + astype = AuthChap; + else if(c->proto == &mschap) + astype = AuthMSchap; + 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(chapchal(&s, astype, chal) < 0) + goto out; + + c->state = "write challenge"; + if(convprint(c, "%s", chal) < 0) + goto out; + + 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(chapresp(&s, user, resp)){ + default: + fprint(2, "factotum: bad result from chapresp\n"); + goto out; + case -1: + goto out; + case 0: + c->state = "write status"; + if(convprint(c, "bad authentication failed") < 0) + goto out; + goto out; + + case 1: + c->state = "write status"; + if(convprint(c, "ok") < 0) + goto out; + goto ok; + } + +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 +chapchal(ServerState *s, int astype, char chal[ChapChallen]) +{ + 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, ChapChallen) <= 5) + return -1; + + s->tr = tr; + return 0; +} + +static int +chapresp(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 +chaproles[] = +{ + "client", chapclient, + "server", chapserver, + 0 +}; + +Proto chap = { +.name= "chap", +.roles= chaproles, +.checkkey= chapcheck, +.keyprompt= "user? !password?", +}; + +Proto mschap = { +.name= "mschap", +.roles= chaproles, +.checkkey= chapcheck, +.keyprompt= "user? !password?", +}; + |