xref: /plan9/sys/src/cmd/auth/factotum/chap.c (revision 6822557b53c0fb0bf9a8ec3fb47e57255de0479a)
1 /*
2  * CHAP, MSCHAP
3  *
4  * The client does not authenticate the server, hence no CAI
5  *
6  * Client protocol:
7  *	write Chapchal
8  *	read response Chapreply or MSchaprely structure
9  *
10  * Server protocol:
11  *	read challenge: 8 bytes binary
12  *	write user: utf8
13  *	write response: Chapreply or MSchapreply structure
14  */
15 
16 #include <ctype.h>
17 #include "dat.h"
18 
19 enum {
20 	ChapChallen = 8,
21 	ChapResplen = 16,
22 	MSchapResplen = 24,
23 };
24 
25 static int dochal(State*);
26 static int doreply(State*, void*, int);
27 static void doLMchap(char *, uchar [ChapChallen], uchar [MSchapResplen]);
28 static void doNTchap(char *, uchar [ChapChallen], uchar [MSchapResplen]);
29 static void dochap(char *, int, char [ChapChallen], uchar [ChapResplen]);
30 
31 
32 struct State
33 {
34 	char *protoname;
35 	int astype;
36 	int asfd;
37 	Key *key;
38 	Ticket	t;
39 	Ticketreq	tr;
40 	char chal[ChapChallen];
41 	MSchapreply mcr;
42 	char cr[ChapResplen];
43 	char err[ERRMAX];
44 	char user[64];
45 	uchar secret[16];	/* for mschap */
46 	int nsecret;
47 };
48 
49 enum
50 {
51 	CNeedChal,
52 	CHaveResp,
53 
54 	SHaveChal,
55 	SNeedUser,
56 	SNeedResp,
57 	SHaveZero,
58 	SHaveCAI,
59 
60 	Maxphase
61 };
62 
63 static char *phasenames[Maxphase] =
64 {
65 [CNeedChal]	"CNeedChal",
66 [CHaveResp]	"CHaveResp",
67 
68 [SHaveChal]	"SHaveChal",
69 [SNeedUser]	"SNeedUser",
70 [SNeedResp]	"SNeedResp",
71 [SHaveZero]	"SHaveZero",
72 [SHaveCAI]	"SHaveCAI",
73 };
74 
75 static int
chapinit(Proto * p,Fsstate * fss)76 chapinit(Proto *p, Fsstate *fss)
77 {
78 	int iscli, ret;
79 	State *s;
80 
81 	if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0)
82 		return failure(fss, nil);
83 
84 	s = emalloc(sizeof *s);
85 	fss->phasename = phasenames;
86 	fss->maxphase = Maxphase;
87 	s->asfd = -1;
88 	if(p == &chap){
89 		s->astype = AuthChap;
90 		s->protoname = "chap";
91 	}else{
92 		s->astype = AuthMSchap;
93 		s->protoname = "mschap";
94 	}
95 
96 	if(iscli)
97 		fss->phase = CNeedChal;
98 	else{
99 		if((ret = findp9authkey(&s->key, fss)) != RpcOk){
100 			free(s);
101 			return ret;
102 		}
103 		if(dochal(s) < 0){
104 			free(s);
105 			return failure(fss, nil);
106 		}
107 		fss->phase = SHaveChal;
108 	}
109 
110 	fss->ps = s;
111 	return RpcOk;
112 }
113 
114 static void
chapclose(Fsstate * fss)115 chapclose(Fsstate *fss)
116 {
117 	State *s;
118 
119 	s = fss->ps;
120 	if(s->asfd >= 0){
121 		close(s->asfd);
122 		s->asfd = -1;
123 	}
124 	free(s);
125 }
126 
127 
128 static int
chapwrite(Fsstate * fss,void * va,uint n)129 chapwrite(Fsstate *fss, void *va, uint n)
130 {
131 	int ret, nreply;
132 	char *a, *v;
133 	void *reply;
134 	Key *k;
135 	Keyinfo ki;
136 	State *s;
137 	Chapreply cr;
138 	MSchapreply mcr;
139 	OChapreply ocr;
140 	OMSchapreply omcr;
141 
142 	s = fss->ps;
143 	a = va;
144 	switch(fss->phase){
145 	default:
146 		return phaseerror(fss, "write");
147 
148 	case CNeedChal:
149 		ret = findkey(&k, mkkeyinfo(&ki, fss, nil), "%s", fss->proto->keyprompt);
150 		if(ret != RpcOk)
151 			return ret;
152 		v = _strfindattr(k->privattr, "!password");
153 		if(v == nil)
154 			return failure(fss, "key has no password");
155 		setattrs(fss->attr, k->attr);
156 		switch(s->astype){
157 		default:
158 			abort();
159 		case AuthMSchap:
160 			doLMchap(v, (uchar *)a, (uchar *)s->mcr.LMresp);
161 			doNTchap(v, (uchar *)a, (uchar *)s->mcr.NTresp);
162 			break;
163 		case AuthChap:
164 			dochap(v, *a, a+1, (uchar *)s->cr);
165 			break;
166 		}
167 		closekey(k);
168 		fss->phase = CHaveResp;
169 		return RpcOk;
170 
171 	case SNeedUser:
172 		if(n >= sizeof s->user)
173 			return failure(fss, "user name too long");
174 		memmove(s->user, va, n);
175 		s->user[n] = '\0';
176 		fss->phase = SNeedResp;
177 		return RpcOk;
178 
179 	case SNeedResp:
180 		switch(s->astype){
181 		default:
182 			return failure(fss, "chap internal botch");
183 		case AuthChap:
184 			if(n != sizeof(Chapreply))
185 				return failure(fss, "did not get Chapreply");
186 			memmove(&cr, va, sizeof cr);
187 			ocr.id = cr.id;
188 			memmove(ocr.resp, cr.resp, sizeof ocr.resp);
189 			memset(omcr.uid, 0, sizeof(omcr.uid));
190 			strecpy(ocr.uid, ocr.uid+sizeof ocr.uid, s->user);
191 			reply = &ocr;
192 			nreply = sizeof ocr;
193 			break;
194 		case AuthMSchap:
195 			if(n != sizeof(MSchapreply))
196 				return failure(fss, "did not get MSchapreply");
197 			memmove(&mcr, va, sizeof mcr);
198 			memmove(omcr.LMresp, mcr.LMresp, sizeof omcr.LMresp);
199 			memmove(omcr.NTresp, mcr.NTresp, sizeof omcr.NTresp);
200 			memset(omcr.uid, 0, sizeof(omcr.uid));
201 			strecpy(omcr.uid, omcr.uid+sizeof omcr.uid, s->user);
202 			reply = &omcr;
203 			nreply = sizeof omcr;
204 			break;
205 		}
206 		if(doreply(s, reply, nreply) < 0)
207 			return failure(fss, nil);
208 		fss->phase = Established;
209 		fss->ai.cuid = s->t.cuid;
210 		fss->ai.suid = s->t.suid;
211 		fss->ai.secret = s->secret;
212 		fss->ai.nsecret = s->nsecret;
213 		fss->haveai = 1;
214 		return RpcOk;
215 	}
216 }
217 
218 static int
chapread(Fsstate * fss,void * va,uint * n)219 chapread(Fsstate *fss, void *va, uint *n)
220 {
221 	State *s;
222 
223 	s = fss->ps;
224 	switch(fss->phase){
225 	default:
226 		return phaseerror(fss, "read");
227 
228 	case CHaveResp:
229 		switch(s->astype){
230 		default:
231 			phaseerror(fss, "write");
232 			break;
233 		case AuthMSchap:
234 			if(*n > sizeof(MSchapreply))
235 				*n = sizeof(MSchapreply);
236 			memmove(va, &s->mcr, *n);
237 			break;
238 		case AuthChap:
239 			if(*n > ChapResplen)
240 				*n = ChapResplen;
241 			memmove(va, s->cr, ChapResplen);
242 			break;
243 		}
244 		fss->phase = Established;
245 		fss->haveai = 0;
246 		return RpcOk;
247 
248 	case SHaveChal:
249 		if(*n > sizeof s->chal)
250 			*n = sizeof s->chal;
251 		memmove(va, s->chal, *n);
252 		fss->phase = SNeedUser;
253 		return RpcOk;
254 	}
255 }
256 
257 static int
dochal(State * s)258 dochal(State *s)
259 {
260 	char *dom, *user;
261 	char trbuf[TICKREQLEN];
262 
263 	s->asfd = -1;
264 
265 	/* send request to authentication server and get challenge */
266 	if((dom = _strfindattr(s->key->attr, "dom")) == nil
267 	|| (user = _strfindattr(s->key->attr, "user")) == nil){
268 		werrstr("chap/dochal cannot happen");
269 		goto err;
270 	}
271 	s->asfd = _authdial(nil, dom);
272 	if(s->asfd < 0)
273 		goto err;
274 
275 	memset(&s->tr, 0, sizeof(s->tr));
276 	s->tr.type = s->astype;
277 	safecpy(s->tr.authdom, dom, sizeof s->tr.authdom);
278 	safecpy(s->tr.hostid, user, sizeof(s->tr.hostid));
279 	convTR2M(&s->tr, trbuf);
280 
281 	if(write(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
282 		goto err;
283 
284 	/* readn, not _asrdresp.  needs to match auth.srv.c. */
285 	if(readn(s->asfd, s->chal, sizeof s->chal) != sizeof s->chal)
286 		goto err;
287 	return 0;
288 
289 err:
290 	if(s->asfd >= 0)
291 		close(s->asfd);
292 	s->asfd = -1;
293 	return -1;
294 }
295 
296 static int
doreply(State * s,void * reply,int nreply)297 doreply(State *s, void *reply, int nreply)
298 {
299 	char ticket[TICKETLEN+AUTHENTLEN];
300 	int n;
301 	Authenticator a;
302 
303 	if((n=write(s->asfd, reply, nreply)) != nreply){
304 		if(n >= 0)
305 			werrstr("short write to auth server");
306 		goto err;
307 	}
308 
309 	if(_asrdresp(s->asfd, ticket, TICKETLEN+AUTHENTLEN) < 0){
310 		/* leave connection open so we can try again */
311 		return -1;
312 	}
313 	s->nsecret = readn(s->asfd, s->secret, sizeof s->secret);
314 	if(s->nsecret < 0)
315 		s->nsecret = 0;
316 	close(s->asfd);
317 	s->asfd = -1;
318 	convM2T(ticket, &s->t, s->key->priv);
319 	if(s->t.num != AuthTs
320 	|| memcmp(s->t.chal, s->tr.chal, sizeof(s->t.chal)) != 0){
321 		if(s->key->successes == 0)
322 			disablekey(s->key);
323 		werrstr(Easproto);
324 		return -1;
325 	}
326 	s->key->successes++;
327 	convM2A(ticket+TICKETLEN, &a, s->t.key);
328 	if(a.num != AuthAc
329 	|| memcmp(a.chal, s->tr.chal, sizeof(a.chal)) != 0
330 	|| a.id != 0){
331 		werrstr(Easproto);
332 		return -1;
333 	}
334 
335 	return 0;
336 err:
337 	if(s->asfd >= 0)
338 		close(s->asfd);
339 	s->asfd = -1;
340 	return -1;
341 }
342 
343 Proto chap = {
344 .name=	"chap",
345 .init=	chapinit,
346 .write=	chapwrite,
347 .read=	chapread,
348 .close=	chapclose,
349 .addkey= replacekey,
350 .keyprompt= "!password?"
351 };
352 
353 Proto mschap = {
354 .name=	"mschap",
355 .init=	chapinit,
356 .write=	chapwrite,
357 .read=	chapread,
358 .close=	chapclose,
359 .addkey= replacekey,
360 .keyprompt= "!password?"
361 };
362 
363 static void
hash(uchar pass[16],uchar c8[ChapChallen],uchar p24[MSchapResplen])364 hash(uchar pass[16], uchar c8[ChapChallen], uchar p24[MSchapResplen])
365 {
366 	int i;
367 	uchar p21[21];
368 	ulong schedule[32];
369 
370 	memset(p21, 0, sizeof p21 );
371 	memmove(p21, pass, 16);
372 
373 	for(i=0; i<3; i++) {
374 		key_setup(p21+i*7, schedule);
375 		memmove(p24+i*8, c8, 8);
376 		block_cipher(schedule, p24+i*8, 0);
377 	}
378 }
379 
380 static void
doNTchap(char * pass,uchar chal[ChapChallen],uchar reply[MSchapResplen])381 doNTchap(char *pass, uchar chal[ChapChallen], uchar reply[MSchapResplen])
382 {
383 	Rune r;
384 	int i, n;
385 	uchar digest[MD4dlen];
386 	uchar *w, unipass[256];
387 
388 	// Standard says unlimited length, experience says 128 max
389 	if ((n = strlen(pass)) > 128)
390 		n = 128;
391 
392 	for(i=0, w=unipass; i < n; i++) {
393 		pass += chartorune(&r, pass);
394 		*w++ = r & 0xff;
395 		*w++ = r >> 8;
396 	}
397 
398 	memset(digest, 0, sizeof digest);
399 	md4(unipass, w-unipass, digest, nil);
400 	memset(unipass, 0, sizeof unipass);
401 	hash(digest, chal, reply);
402 }
403 
404 static void
doLMchap(char * pass,uchar chal[ChapChallen],uchar reply[MSchapResplen])405 doLMchap(char *pass, uchar chal[ChapChallen], uchar reply[MSchapResplen])
406 {
407 	int i;
408 	ulong schedule[32];
409 	uchar p14[15], p16[16];
410 	uchar s8[8] = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
411 	int n = strlen(pass);
412 
413 	if(n > 14){
414 		// let prudent people avoid the LM vulnerability
415 		//   and protect the loop below from buffer overflow
416 		memset(reply, 0, MSchapResplen);
417 		return;
418 	}
419 
420 	// Spec says space padded, experience says otherwise
421 	memset(p14, 0, sizeof p14 -1);
422 	p14[sizeof p14 - 1] = '\0';
423 
424 	// NT4 requires uppercase, Win XP doesn't care
425 	for (i = 0; pass[i]; i++)
426 		p14[i] = islower(pass[i])? toupper(pass[i]): pass[i];
427 
428 	for(i=0; i<2; i++) {
429 		key_setup(p14+i*7, schedule);
430 		memmove(p16+i*8, s8, 8);
431 		block_cipher(schedule, p16+i*8, 0);
432 	}
433 
434 	memset(p14, 0, sizeof p14);
435 	hash(p16, chal, reply);
436 }
437 
438 static void
dochap(char * pass,int id,char chal[ChapChallen],uchar resp[ChapResplen])439 dochap(char *pass, int id, char chal[ChapChallen], uchar resp[ChapResplen])
440 {
441 	char buf[1+ChapChallen+MAXNAMELEN+1];
442 	int n = strlen(pass);
443 
444 	*buf = id;
445 	if (n > MAXNAMELEN)
446 		n = MAXNAMELEN-1;
447 	memset(buf, 0, sizeof buf);
448 	strncpy(buf+1, pass, n);
449 	memmove(buf+1+n, chal, ChapChallen);
450 	md5((uchar*)buf, 1+n+ChapChallen, resp, nil);
451 }
452 
453