Blob


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 */
26 #include "std.h"
27 #include "dat.h"
29 enum {
30 ChapChallen = 8,
32 MShashlen = 16,
33 MSchallen = 8,
34 MSresplen = 24,
35 };
37 static int
38 chapcheck(Key *k)
39 {
40 if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){
41 werrstr("need user and !password attributes");
42 return -1;
43 }
44 return 0;
45 }
47 static void
48 nthash(uchar hash[MShashlen], char *passwd)
49 {
50 uchar buf[512];
51 int i;
53 for(i=0; *passwd && i<sizeof(buf); passwd++) {
54 buf[i++] = *passwd;
55 buf[i++] = 0;
56 }
58 memset(hash, 0, 16);
60 md4(buf, i, hash, 0);
61 }
63 static void
64 desencrypt(uchar data[8], uchar key[7])
65 {
66 ulong ekey[32];
68 key_setup(key, ekey);
69 block_cipher(ekey, data, 0);
70 }
72 static void
73 lmhash(uchar hash[MShashlen], char *passwd)
74 {
75 uchar buf[14];
76 char *stdtext = "KGS!@#$%";
77 int i;
79 strncpy((char*)buf, passwd, sizeof(buf));
80 for(i=0; i<sizeof(buf); i++)
81 if(buf[i] >= 'a' && buf[i] <= 'z')
82 buf[i] += 'A' - 'a';
84 memset(hash, 0, 16);
85 memcpy(hash, stdtext, 8);
86 memcpy(hash+8, stdtext, 8);
88 desencrypt(hash, buf);
89 desencrypt(hash+8, buf+7);
90 }
92 static void
93 mschalresp(uchar resp[MSresplen], uchar hash[MShashlen], uchar chal[MSchallen])
94 {
95 int i;
96 uchar buf[21];
98 memset(buf, 0, sizeof(buf));
99 memcpy(buf, hash, MShashlen);
101 for(i=0; i<3; i++) {
102 memmove(resp+i*MSchallen, chal, MSchallen);
103 desencrypt(resp+i*MSchallen, buf+i*7);
107 static int
108 chapclient(Conv *c)
110 int id, astype, nchal, npw, ret;
111 uchar *chal;
112 char *s, *pw, *user, *res;
113 Attr *attr;
114 Key *k;
115 Chapreply cr;
116 MSchapreply mscr;
117 DigestState *ds;
119 ret = -1;
120 chal = nil;
121 k = nil;
122 attr = c->attr;
124 if(c->proto == &chap){
125 astype = AuthChap;
126 s = strfindattr(attr, "id");
127 if(s == nil || *s == 0){
128 werrstr("need id=n attr in start message");
129 goto out;
131 id = strtol(s, &s, 10);
132 if(*s != 0 || id < 0 || id >= 256){
133 werrstr("bad id=n attr in start message");
134 goto out;
136 cr.id = id;
137 }else if(c->proto == &mschap)
138 astype = AuthMSchap;
139 else{
140 werrstr("bad proto");
141 goto out;
144 c->state = "find key";
145 k = keyfetch(c, "%A %s", attr, c->proto->keyprompt);
146 if(k == nil)
147 goto out;
149 c->attr = addattrs(copyattr(attr), k->attr);
151 c->state = "read challenge";
152 if((nchal = convreadm(c, (char**)&chal)) < 0)
153 goto out;
154 if(astype == AuthMSchap && nchal != MSchallen)
155 c->state = "write user";
156 if((user = strfindattr(k->attr, "user")) == nil){
157 werrstr("key has no user (cannot happen?)");
158 goto out;
160 if(convprint(c, "%s", user) < 0)
161 goto out;
163 c->state = "write response";
164 if((pw = strfindattr(k->privattr, "!password")) == nil){
165 werrstr("key has no password (cannot happen?)");
166 goto out;
168 npw = strlen(pw);
170 if(astype == AuthChap){
171 ds = md5(&cr.id, 1, 0, 0);
172 md5((uchar*)pw, npw, 0, ds);
173 md5(chal, nchal, (uchar*)cr.resp, ds);
174 if(convwrite(c, &cr, sizeof cr) < 0)
175 goto out;
176 }else{
177 uchar hash[MShashlen];
179 memset(&mscr, 0, sizeof mscr);
180 if(strfindattr(k->attr, "lm")){
181 lmhash(hash, pw);
182 mschalresp((uchar*)mscr.LMresp, hash, chal);
183 }else{
184 nthash(hash, pw);
185 mschalresp((uchar*)mscr.NTresp, hash, chal);
187 if(convwrite(c, &mscr, sizeof mscr) < 0)
188 goto out;
191 c->state = "read result";
192 if(convreadm(c, &res) < 0)
193 goto out;
194 if(strcmp(res, "ok") == 0){
195 ret = 0;
196 werrstr("succeeded");
197 goto out;
199 if(strncmp(res, "bad ", 4) != 0){
200 werrstr("bad result: %s", res);
201 goto out;
204 c->state = "replace key";
205 keyevict(c, k, "%s", res+4);
206 werrstr("%s", res+4);
208 out:
209 free(res);
210 keyclose(k);
211 free(chal);
212 if(c->attr != attr)
213 freeattr(attr);
214 return ret;
217 /* shared with auth dialing routines */
218 typedef struct ServerState ServerState;
219 struct ServerState
221 int asfd;
222 Key *k;
223 Ticketreq tr;
224 Ticket t;
225 char *dom;
226 char *hostid;
227 };
229 static int chapchal(ServerState*, int, char[ChapChallen]);
230 static int chapresp(ServerState*, char*, char*);
232 static int
233 chapserver(Conv *c)
235 char chal[ChapChallen], *user, *resp;
236 ServerState s;
237 int astype, ret;
238 Attr *a;
240 ret = -1;
241 user = nil;
242 resp = nil;
243 memset(&s, 0, sizeof s);
244 s.asfd = -1;
246 if(c->proto == &chap)
247 astype = AuthChap;
248 else if(c->proto == &mschap)
249 astype = AuthMSchap;
250 else{
251 werrstr("bad proto");
252 goto out;
255 c->state = "find key";
256 if((s.k = plan9authkey(c->attr)) == nil)
257 goto out;
259 a = copyattr(s.k->attr);
260 a = delattr(a, "proto");
261 c->attr = addattrs(c->attr, a);
262 freeattr(a);
264 c->state = "authdial";
265 s.hostid = strfindattr(s.k->attr, "user");
266 s.dom = strfindattr(s.k->attr, "dom");
267 if((s.asfd = xioauthdial(nil, s.dom)) < 0){
268 werrstr("authdial %s: %r", s.dom);
269 goto out;
272 c->state = "authchal";
273 if(chapchal(&s, astype, chal) < 0)
274 goto out;
276 c->state = "write challenge";
277 if(convprint(c, "%s", chal) < 0)
278 goto out;
280 c->state = "read user";
281 if(convreadm(c, &user) < 0)
282 goto out;
284 c->state = "read response";
285 if(convreadm(c, &resp) < 0)
286 goto out;
288 c->state = "authwrite";
289 switch(chapresp(&s, user, resp)){
290 default:
291 fprint(2, "factotum: bad result from chapresp\n");
292 goto out;
293 case -1:
294 goto out;
295 case 0:
296 c->state = "write status";
297 if(convprint(c, "bad authentication failed") < 0)
298 goto out;
299 goto out;
301 case 1:
302 c->state = "write status";
303 if(convprint(c, "ok") < 0)
304 goto out;
305 goto ok;
308 ok:
309 ret = 0;
310 c->attr = addcap(c->attr, c->sysuser, &s.t);
312 out:
313 keyclose(s.k);
314 free(user);
315 free(resp);
316 // xioclose(s.asfd);
317 return ret;
320 static int
321 chapchal(ServerState *s, int astype, char chal[ChapChallen])
323 char trbuf[TICKREQLEN];
324 Ticketreq tr;
326 memset(&tr, 0, sizeof tr);
328 tr.type = astype;
330 if(strlen(s->hostid) >= sizeof tr.hostid){
331 werrstr("hostid too long");
332 return -1;
334 strcpy(tr.hostid, s->hostid);
336 if(strlen(s->dom) >= sizeof tr.authdom){
337 werrstr("domain too long");
338 return -1;
340 strcpy(tr.authdom, s->dom);
342 convTR2M(&tr, trbuf);
343 if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
344 return -1;
346 if(xioasrdresp(s->asfd, chal, ChapChallen) <= 5)
347 return -1;
349 s->tr = tr;
350 return 0;
353 static int
354 chapresp(ServerState *s, char *user, char *resp)
356 char tabuf[TICKETLEN+AUTHENTLEN];
357 char trbuf[TICKREQLEN];
358 int len;
359 Authenticator a;
360 Ticket t;
361 Ticketreq tr;
363 tr = s->tr;
364 if(memrandom(tr.chal, CHALLEN) < 0)
365 return -1;
367 if(strlen(user) >= sizeof tr.uid){
368 werrstr("uid too long");
369 return -1;
371 strcpy(tr.uid, user);
373 convTR2M(&tr, trbuf);
374 if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
375 return -1;
377 len = strlen(resp);
378 if(xiowrite(s->asfd, resp, len) != len)
379 return -1;
381 if(xioasrdresp(s->asfd, tabuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN)
382 return 0;
384 convM2T(tabuf, &t, s->k->priv);
385 if(t.num != AuthTs
386 || memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){
387 werrstr("key mismatch with auth server");
388 return -1;
391 convM2A(tabuf+TICKETLEN, &a, t.key);
392 if(a.num != AuthAc
393 || memcmp(a.chal, tr.chal, sizeof a.chal) != 0
394 || a.id != 0){
395 werrstr("key2 mismatch with auth server");
396 return -1;
399 s->t = t;
400 return 1;
403 static Role
404 chaproles[] =
406 "client", chapclient,
407 "server", chapserver,
409 };
411 Proto chap = {
412 .name= "chap",
413 .roles= chaproles,
414 .checkkey= chapcheck,
415 .keyprompt= "user? !password?",
416 };
418 Proto mschap = {
419 .name= "mschap",
420 .roles= chaproles,
421 .checkkey= chapcheck,
422 .keyprompt= "user? !password?",
423 };