tchap.c - plan9port - [fork] Plan 9 from user space
(HTM) git clone git://src.adamsgaard.dk/plan9port
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
tchap.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 };