| /* |
| * 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; |
| res = nil; |
| |
| 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 = { |
| "chap", |
| chaproles, |
| "user? !password?", |
| chapcheck |
| }; |
| |
| Proto mschap = { |
| "mschap", |
| chaproles, |
| "user? !password?", |
| chapcheck |
| }; |
| |
| |