plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

chap.c (8049B)


      1 /*
      2  * CHAP, MSCHAP
      3  *
      4  * The client does not authenticate the server, hence no CAI
      5  *
      6  * Protocol:
      7  *
      8  *	S -> C: random 8-byte challenge
      9  *	C -> S: user in UTF-8
     10  *	C -> S: Chapreply or MSchapreply structure
     11  *	S -> C: ok or 'bad why'
     12  *
     13  * The chap protocol requires the client to give it id=%d, the id of
     14  * the PPP message containing the challenge, which is used
     15  * as part of the response.  Because the client protocol is message-id
     16  * specific, there is no point in looping to try multiple keys.
     17  *
     18  * The MS chap protocol actually uses two different hashes, an
     19  * older insecure one called the LM (Lan Manager) hash, and a newer
     20  * more secure one called the NT hash.  By default we send back only
     21  * the NT hash, because the LM hash can help an eavesdropper run
     22  * a brute force attack.  If the key has an lm attribute, then we send only the
     23  * LM hash.
     24  */
     25 
     26 #include "std.h"
     27 #include "dat.h"
     28 
     29 extern Proto chap, mschap;
     30 
     31 enum {
     32 	ChapChallen = 8,
     33 
     34 	MShashlen = 16,
     35 	MSchallen = 8,
     36 	MSresplen = 24
     37 };
     38 
     39 static int
     40 chapcheck(Key *k)
     41 {
     42 	if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){
     43 		werrstr("need user and !password attributes");
     44 		return -1;
     45 	}
     46 	return 0;
     47 }
     48 
     49 static void
     50 nthash(uchar hash[MShashlen], char *passwd)
     51 {
     52 	uchar buf[512];
     53 	int i;
     54 
     55 	for(i=0; *passwd && i<sizeof(buf); passwd++) {
     56 		buf[i++] = *passwd;
     57 		buf[i++] = 0;
     58 	}
     59 
     60 	memset(hash, 0, 16);
     61 
     62 	md4(buf, i, hash, 0);
     63 }
     64 
     65 static void
     66 desencrypt(uchar data[8], uchar key[7])
     67 {
     68 	ulong ekey[32];
     69 
     70 	key_setup(key, ekey);
     71 	block_cipher(ekey, data, 0);
     72 }
     73 
     74 static void
     75 lmhash(uchar hash[MShashlen], char *passwd)
     76 {
     77 	uchar buf[14];
     78 	char *stdtext = "KGS!@#$%";
     79 	int i;
     80 
     81 	strncpy((char*)buf, passwd, sizeof(buf));
     82 	for(i=0; i<sizeof(buf); i++)
     83 		if(buf[i] >= 'a' && buf[i] <= 'z')
     84 			buf[i] += 'A' - 'a';
     85 
     86 	memset(hash, 0, 16);
     87 	memcpy(hash, stdtext, 8);
     88 	memcpy(hash+8, stdtext, 8);
     89 
     90 	desencrypt(hash, buf);
     91 	desencrypt(hash+8, buf+7);
     92 }
     93 
     94 static void
     95 mschalresp(uchar resp[MSresplen], uchar hash[MShashlen], uchar chal[MSchallen])
     96 {
     97 	int i;
     98 	uchar buf[21];
     99 
    100 	memset(buf, 0, sizeof(buf));
    101 	memcpy(buf, hash, MShashlen);
    102 
    103 	for(i=0; i<3; i++) {
    104 		memmove(resp+i*MSchallen, chal, MSchallen);
    105 		desencrypt(resp+i*MSchallen, buf+i*7);
    106 	}
    107 }
    108 
    109 static int
    110 chapclient(Conv *c)
    111 {
    112 	int id, astype, nchal, npw, ret;
    113 	uchar *chal;
    114 	char *s, *pw, *user, *res;
    115 	Attr *attr;
    116 	Key *k;
    117 	Chapreply cr;
    118 	MSchapreply mscr;
    119 	DigestState *ds;
    120 
    121 	ret = -1;
    122 	chal = nil;
    123 	k = nil;
    124 	attr = c->attr;
    125 	res = nil;
    126 
    127 	if(c->proto == &chap){
    128 		astype = AuthChap;
    129 		s = strfindattr(attr, "id");
    130 		if(s == nil || *s == 0){
    131 			werrstr("need id=n attr in start message");
    132 			goto out;
    133 		}
    134 		id = strtol(s, &s, 10);
    135 		if(*s != 0 || id < 0 || id >= 256){
    136 			werrstr("bad id=n attr in start message");
    137 			goto out;
    138 		}
    139 		cr.id = id;
    140 	}else if(c->proto == &mschap)
    141 		astype = AuthMSchap;
    142 	else{
    143 		werrstr("bad proto");
    144 		goto out;
    145 	}
    146 
    147 	c->state = "find key";
    148 	k = keyfetch(c, "%A %s", attr, c->proto->keyprompt);
    149 	if(k == nil)
    150 		goto out;
    151 
    152 	c->attr = addattrs(copyattr(attr), k->attr);
    153 
    154 	c->state = "read challenge";
    155 	if((nchal = convreadm(c, (char**)(void*)&chal)) < 0)
    156 		goto out;
    157 	if(astype == AuthMSchap && nchal != MSchallen)
    158 	c->state = "write user";
    159 	if((user = strfindattr(k->attr, "user")) == nil){
    160 		werrstr("key has no user (cannot happen?)");
    161 		goto out;
    162 	}
    163 	if(convprint(c, "%s", user) < 0)
    164 		goto out;
    165 
    166 	c->state = "write response";
    167 	if((pw = strfindattr(k->privattr, "!password")) == nil){
    168 		werrstr("key has no password (cannot happen?)");
    169 		goto out;
    170 	}
    171 	npw = strlen(pw);
    172 
    173 	if(astype == AuthChap){
    174 		ds = md5(&cr.id, 1, 0, 0);
    175 		md5((uchar*)pw, npw, 0, ds);
    176 		md5(chal, nchal, (uchar*)cr.resp, ds);
    177 		if(convwrite(c, &cr, sizeof cr) < 0)
    178 			goto out;
    179 	}else{
    180 		uchar hash[MShashlen];
    181 
    182 		memset(&mscr, 0, sizeof mscr);
    183 		if(strfindattr(k->attr, "lm")){
    184 			lmhash(hash, pw);
    185 			mschalresp((uchar*)mscr.LMresp, hash, chal);
    186 		}else{
    187 			nthash(hash, pw);
    188 			mschalresp((uchar*)mscr.NTresp, hash, chal);
    189 		}
    190 		if(convwrite(c, &mscr, sizeof mscr) < 0)
    191 			goto out;
    192 	}
    193 
    194 	c->state = "read result";
    195 	if(convreadm(c, &res) < 0)
    196 		goto out;
    197 	if(strcmp(res, "ok") == 0){
    198 		ret = 0;
    199 		werrstr("succeeded");
    200 		goto out;
    201 	}
    202 	if(strncmp(res, "bad ", 4) != 0){
    203 		werrstr("bad result: %s", res);
    204 		goto out;
    205 	}
    206 
    207 	c->state = "replace key";
    208 	keyevict(c, k, "%s", res+4);
    209 	werrstr("%s", res+4);
    210 
    211 out:
    212 	free(res);
    213 	keyclose(k);
    214 	free(chal);
    215 	if(c->attr != attr)
    216 		freeattr(attr);
    217 	return ret;
    218 }
    219 
    220 /* shared with auth dialing routines */
    221 typedef struct ServerState ServerState;
    222 struct ServerState
    223 {
    224 	int asfd;
    225 	Key *k;
    226 	Ticketreq tr;
    227 	Ticket t;
    228 	char *dom;
    229 	char *hostid;
    230 };
    231 
    232 static int chapchal(ServerState*, int, char[ChapChallen]);
    233 static int chapresp(ServerState*, char*, char*);
    234 
    235 static int
    236 chapserver(Conv *c)
    237 {
    238 	char chal[ChapChallen], *user, *resp;
    239 	ServerState s;
    240 	int astype, ret;
    241 	Attr *a;
    242 
    243 	ret = -1;
    244 	user = nil;
    245 	resp = nil;
    246 	memset(&s, 0, sizeof s);
    247 	s.asfd = -1;
    248 
    249 	if(c->proto == &chap)
    250 		astype = AuthChap;
    251 	else if(c->proto == &mschap)
    252 		astype = AuthMSchap;
    253 	else{
    254 		werrstr("bad proto");
    255 		goto out;
    256 	}
    257 
    258 	c->state = "find key";
    259 	if((s.k = plan9authkey(c->attr)) == nil)
    260 		goto out;
    261 
    262 	a = copyattr(s.k->attr);
    263 	a = delattr(a, "proto");
    264 	c->attr = addattrs(c->attr, a);
    265 	freeattr(a);
    266 
    267 	c->state = "authdial";
    268 	s.hostid = strfindattr(s.k->attr, "user");
    269 	s.dom = strfindattr(s.k->attr, "dom");
    270 	if((s.asfd = xioauthdial(nil, s.dom)) < 0){
    271 		werrstr("authdial %s: %r", s.dom);
    272 		goto out;
    273 	}
    274 
    275 	c->state = "authchal";
    276 	if(chapchal(&s, astype, chal) < 0)
    277 		goto out;
    278 
    279 	c->state = "write challenge";
    280 	if(convprint(c, "%s", chal) < 0)
    281 		goto out;
    282 
    283 	c->state = "read user";
    284 	if(convreadm(c, &user) < 0)
    285 		goto out;
    286 
    287 	c->state = "read response";
    288 	if(convreadm(c, &resp) < 0)
    289 		goto out;
    290 
    291 	c->state = "authwrite";
    292 	switch(chapresp(&s, user, resp)){
    293 	default:
    294 		fprint(2, "factotum: bad result from chapresp\n");
    295 		goto out;
    296 	case -1:
    297 		goto out;
    298 	case 0:
    299 		c->state = "write status";
    300 		if(convprint(c, "bad authentication failed") < 0)
    301 			goto out;
    302 		goto out;
    303 
    304 	case 1:
    305 		c->state = "write status";
    306 		if(convprint(c, "ok") < 0)
    307 			goto out;
    308 		goto ok;
    309 	}
    310 
    311 ok:
    312 	ret = 0;
    313 	c->attr = addcap(c->attr, c->sysuser, &s.t);
    314 
    315 out:
    316 	keyclose(s.k);
    317 	free(user);
    318 	free(resp);
    319 /*	xioclose(s.asfd); */
    320 	return ret;
    321 }
    322 
    323 static int
    324 chapchal(ServerState *s, int astype, char chal[ChapChallen])
    325 {
    326 	char trbuf[TICKREQLEN];
    327 	Ticketreq tr;
    328 
    329 	memset(&tr, 0, sizeof tr);
    330 
    331 	tr.type = astype;
    332 
    333 	if(strlen(s->hostid) >= sizeof tr.hostid){
    334 		werrstr("hostid too long");
    335 		return -1;
    336 	}
    337 	strcpy(tr.hostid, s->hostid);
    338 
    339 	if(strlen(s->dom) >= sizeof tr.authdom){
    340 		werrstr("domain too long");
    341 		return -1;
    342 	}
    343 	strcpy(tr.authdom, s->dom);
    344 
    345 	convTR2M(&tr, trbuf);
    346 	if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
    347 		return -1;
    348 
    349 	if(xioasrdresp(s->asfd, chal, ChapChallen) <= 5)
    350 		return -1;
    351 
    352 	s->tr = tr;
    353 	return 0;
    354 }
    355 
    356 static int
    357 chapresp(ServerState *s, char *user, char *resp)
    358 {
    359 	char tabuf[TICKETLEN+AUTHENTLEN];
    360 	char trbuf[TICKREQLEN];
    361 	int len;
    362 	Authenticator a;
    363 	Ticket t;
    364 	Ticketreq tr;
    365 
    366 	tr = s->tr;
    367 	if(memrandom(tr.chal, CHALLEN) < 0)
    368 		return -1;
    369 
    370 	if(strlen(user) >= sizeof tr.uid){
    371 		werrstr("uid too long");
    372 		return -1;
    373 	}
    374 	strcpy(tr.uid, user);
    375 
    376 	convTR2M(&tr, trbuf);
    377 	if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
    378 		return -1;
    379 
    380 	len = strlen(resp);
    381 	if(xiowrite(s->asfd, resp, len) != len)
    382 		return -1;
    383 
    384 	if(xioasrdresp(s->asfd, tabuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN)
    385 		return 0;
    386 
    387 	convM2T(tabuf, &t, s->k->priv);
    388 	if(t.num != AuthTs
    389 	|| memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){
    390 		werrstr("key mismatch with auth server");
    391 		return -1;
    392 	}
    393 
    394 	convM2A(tabuf+TICKETLEN, &a, t.key);
    395 	if(a.num != AuthAc
    396 	|| memcmp(a.chal, tr.chal, sizeof a.chal) != 0
    397 	|| a.id != 0){
    398 		werrstr("key2 mismatch with auth server");
    399 		return -1;
    400 	}
    401 
    402 	s->t = t;
    403 	return 1;
    404 }
    405 
    406 static Role
    407 chaproles[] =
    408 {
    409 	"client",	chapclient,
    410 	"server",	chapserver,
    411 	0
    412 };
    413 
    414 Proto chap = {
    415 	"chap",
    416 	chaproles,
    417 	"user? !password?",
    418 	chapcheck
    419 };
    420 
    421 Proto mschap = {
    422 	"mschap",
    423 	chaproles,
    424 	"user? !password?",
    425 	chapcheck
    426 };