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 };