2005-02-13 05:59:29 +00:00
|
|
|
/*
|
|
|
|
|
* 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,
|
2006-04-01 19:24:03 +00:00
|
|
|
MSresplen = 24
|
2005-02-13 05:59:29 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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);
|
2006-04-01 19:24:03 +00:00
|
|
|
/* xioclose(s.asfd); */
|
2005-02-13 05:59:29 +00:00
|
|
|
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 = {
|
2006-04-01 19:24:03 +00:00
|
|
|
"chap",
|
|
|
|
|
chaproles,
|
|
|
|
|
"user? !password?",
|
|
|
|
|
chapcheck
|
2005-02-13 05:59:29 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Proto mschap = {
|
2006-04-01 19:24:03 +00:00
|
|
|
"mschap",
|
|
|
|
|
chaproles,
|
|
|
|
|
"user? !password?",
|
|
|
|
|
chapcheck
|
2005-02-13 05:59:29 +00:00
|
|
|
};
|
|
|
|
|
|
2006-04-01 19:24:03 +00:00
|
|
|
|