xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.bin/pppd/chap_ms.c (revision 4f62a6570dcd50f97b2c01e95cbb683b3f9d7a7c)
1 /*
2  * chap_ms.c - Microsoft MS-CHAP compatible implementation.
3  *
4  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
5  * Use is subject to license terms.
6  *
7  * Copyright (c) 1995 Eric Rosenquist, Strata Software Limited.
8  * http://www.strataware.com/
9  *
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms are permitted
13  * provided that the above copyright notice and this paragraph are
14  * duplicated in all such forms and that any documentation,
15  * advertising materials, and other materials related to such
16  * distribution and use acknowledge that the software was developed
17  * by Eric Rosenquist.  The name of the author may not be used to
18  * endorse or promote products derived from this software without
19  * specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
22  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
23  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
24  */
25 
26 /*
27  * This module implements MS-CHAPv1 (RFC 2433) and MS-CHAPv2 (RFC 2759).
28  *
29  * Modifications by Lauri Pesonen / lpesonen@clinet.fi, april 1997
30  *
31  *   Implemented LANManager type password response to MS-CHAP challenges.
32  *   Now pppd provides both NT style and LANMan style blocks, and the
33  *   prefered is set by option "ms-lanman". Default is to use NT.
34  *   The hash text (StdText) was taken from Win95 RASAPI32.DLL.
35  *
36  *   You should also use DOMAIN\\USERNAME as described in README.MSCHAP80
37  *
38  * Modifications by James Carlson / james.d.carlson@sun.com, June 1st, 2000.
39  *
40  *	Added MS-CHAPv2 support.
41  */
42 
43 #if defined(CHAPMS) || defined(CHAPMSV2)
44 
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <ctype.h>
49 #include <sys/types.h>
50 #include <sys/time.h>
51 #include <unistd.h>
52 #ifdef HAVE_CRYPT_H
53 #include <crypt.h>
54 #endif
55 
56 #ifdef CHAPMSV2
57 #include "sha1.h"
58 #endif
59 
60 #ifndef USE_CRYPT
61 #include <des.h>
62 #endif
63 
64 #include "pppd.h"
65 #include "chap.h"
66 #include "chap_ms.h"
67 #include "md4.h"
68 
69 typedef struct {
70     u_char LANManResp[24];
71     u_char NTResp[24];
72     u_char UseNT;		/* If 1, ignore the LANMan response field */
73 } MS_ChapResponse;
74 /* We use MS_CHAP_RESPONSE_LEN, rather than sizeof(MS_ChapResponse),
75    in case this struct gets padded. */
76 
77 typedef struct {
78 	u_char PeerChallenge[16];
79 	u_char MustBeZero[8];
80 	u_char NTResp[24];
81 	u_char Flags;		/* Should be zero (Win98 sends 04) */
82 } MS_Chapv2Response;
83 /* We use MS_CHAPV2_RESPONSE_LEN, rather than sizeof(MS_Chapv2Response),
84    in case this struct gets padded. */
85 
86 static void	ChallengeResponse __P((u_char *, u_char *, u_char *));
87 static void	DesEncrypt __P((u_char *, u_char *, u_char *));
88 static void	MakeKey __P((u_char *, u_char *));
89 static u_char	Get7Bits __P((u_char *, int));
90 #ifdef CHAPMS
91 static void	ChapMS_NT __P((u_char *, char *, int, MS_ChapResponse *));
92 #ifdef MSLANMAN
93 static void	ChapMS_LANMan __P((u_char *, char *, int, MS_ChapResponse *));
94 #endif
95 #endif
96 #ifdef CHAPMSV2
97 static void	ChapMSv2_NT __P((char *, u_char *, char *, int,
98     MS_Chapv2Response *));
99 #endif
100 
101 #ifdef USE_CRYPT
102 static void	Expand __P((u_char *, char *));
103 static void	Collapse __P((char *, u_char *));
104 #endif
105 
106 #if defined(MSLANMAN) && defined(CHAPMS)
107 bool	ms_lanman = 0;    	/* Use LanMan password instead of NT */
108 			  	/* Has meaning only with MS-CHAP challenges */
109 #endif
110 
111 #ifdef CHAPMSV2
112 /* Specially-formatted Microsoft CHAP response message. */
113 static char status_message[256];
114 #endif
115 
116 static void
ChallengeResponse(challenge,pwHash,response)117 ChallengeResponse(challenge, pwHash, response)
118     u_char *challenge;	/* IN   8 octets */
119     u_char *pwHash;	/* IN  16 octets */
120     u_char *response;	/* OUT 24 octets */
121 {
122     u_char    ZPasswordHash[21];
123 
124     BZERO(ZPasswordHash, sizeof(ZPasswordHash));
125     BCOPY(pwHash, ZPasswordHash, MD4_SIGNATURE_SIZE);
126 
127 #if 0
128     dbglog("ChallengeResponse - ZPasswordHash %.*B",
129 	   sizeof(ZPasswordHash), ZPasswordHash);
130 #endif
131 
132     DesEncrypt(challenge, ZPasswordHash +  0, response + 0);
133     DesEncrypt(challenge, ZPasswordHash +  7, response + 8);
134     DesEncrypt(challenge, ZPasswordHash + 14, response + 16);
135 
136 #if 0
137     dbglog("ChallengeResponse - response %.24B", response);
138 #endif
139 }
140 
141 
142 #ifdef USE_CRYPT
143 static void
DesEncrypt(clear,key,cipher)144 DesEncrypt(clear, key, cipher)
145     u_char *clear;	/* IN  8 octets */
146     u_char *key;	/* IN  7 octets */
147     u_char *cipher;	/* OUT 8 octets */
148 {
149     u_char des_key[8];
150     char crypt_key[66];
151     char des_input[66];
152 
153     MakeKey(key, des_key);
154 
155     Expand(des_key, crypt_key);
156     setkey(crypt_key);
157 
158 #if 0
159     CHAPDEBUG((LOG_INFO, "DesEncrypt: 8 octet input : %.8B", clear));
160 #endif
161 
162     Expand(clear, des_input);
163     encrypt(des_input, 0);
164     Collapse(des_input, cipher);
165 
166 #if 0
167     CHAPDEBUG((LOG_INFO, "DesEncrypt: 8 octet output: %.8B", cipher));
168 #endif
169 }
170 
171 #else /* USE_CRYPT */
172 
173 static void
DesEncrypt(clear,key,cipher)174 DesEncrypt(clear, key, cipher)
175     u_char *clear;	/* IN  8 octets */
176     u_char *key;	/* IN  7 octets */
177     u_char *cipher;	/* OUT 8 octets */
178 {
179     des_cblock		des_key;
180     des_key_schedule	key_schedule;
181 
182     MakeKey(key, des_key);
183 
184     des_set_key(&des_key, key_schedule);
185 
186 #if 0
187     CHAPDEBUG((LOG_INFO, "DesEncrypt: 8 octet input : %.8B", clear));
188 #endif
189 
190     des_ecb_encrypt((des_cblock *)clear, (des_cblock *)cipher, key_schedule, 1);
191 
192 #if 0
193     CHAPDEBUG((LOG_INFO, "DesEncrypt: 8 octet output: %.8B", cipher));
194 #endif
195 }
196 
197 #endif /* USE_CRYPT */
198 
199 
Get7Bits(input,startBit)200 static u_char Get7Bits(input, startBit)
201     u_char *input;
202     int startBit;
203 {
204     register unsigned int	word;
205 
206     word  = (unsigned)input[startBit / 8] << 8;
207     word |= (unsigned)input[startBit / 8 + 1];
208 
209     word >>= 15 - (startBit % 8 + 7);
210 
211     return word & 0xFE;
212 }
213 
214 #ifdef USE_CRYPT
215 
216 /* in == 8-byte string (expanded version of the 56-bit key)
217  * out == 64-byte string where each byte is either 1 or 0
218  * Note that the low-order "bit" is always ignored by by setkey()
219  */
Expand(in,out)220 static void Expand(in, out)
221     u_char *in;
222     char *out;
223 {
224         int j, c;
225         int i;
226 
227         for(i = 0; i < 64; in++){
228 		c = *in;
229                 for(j = 7; j >= 0; j--)
230                         *out++ = (c >> j) & 01;
231                 i += 8;
232         }
233 }
234 
235 /* The inverse of Expand
236  */
Collapse(in,out)237 static void Collapse(in, out)
238     char *in;
239     u_char *out;
240 {
241         int j;
242         int i;
243 	unsigned int c;
244 
245 	for (i = 0; i < 64; i += 8, out++) {
246 	    c = 0;
247 	    for (j = 7; j >= 0; j--, in++)
248 		c |= *(u_char *)in << j;
249 	    *out = c & 0xff;
250 	}
251 }
252 #endif
253 
MakeKey(key,des_key)254 static void MakeKey(key, des_key)
255     u_char *key;	/* IN  56 bit DES key missing parity bits */
256     u_char *des_key;	/* OUT 64 bit DES key with parity bits added */
257 {
258     des_key[0] = Get7Bits(key,  0);
259     des_key[1] = Get7Bits(key,  7);
260     des_key[2] = Get7Bits(key, 14);
261     des_key[3] = Get7Bits(key, 21);
262     des_key[4] = Get7Bits(key, 28);
263     des_key[5] = Get7Bits(key, 35);
264     des_key[6] = Get7Bits(key, 42);
265     des_key[7] = Get7Bits(key, 49);
266 
267 #ifndef USE_CRYPT
268     des_set_odd_parity((des_cblock *)des_key);
269 #endif
270 
271 #if 0
272     CHAPDEBUG((LOG_INFO, "MakeKey: 56-bit input : %.7B", key));
273     CHAPDEBUG((LOG_INFO, "MakeKey: 64-bit output: %.8B", des_key));
274 #endif
275 }
276 
277 #ifdef CHAPMS
278 static void
ChapMS_NT(rchallenge,secret,secret_len,response)279 ChapMS_NT(rchallenge, secret, secret_len, response)
280     u_char *rchallenge;
281     char *secret;
282     int secret_len;
283     MS_ChapResponse    *response;
284 {
285     int			i;
286 #if defined(__NetBSD__) || defined(HAVE_LIBMD)
287     /* NetBSD uses the libc md4 routines which take bytes instead of bits */
288     int			mdlen = secret_len * 2;
289 #else
290     int			mdlen = secret_len * 2 * 8;
291 #endif
292     MD4_CTX		md4Context;
293     u_char		hash[MD4_SIGNATURE_SIZE];
294     u_char		unicodePassword[MAX_NT_PASSWORD * 2];
295 
296     /* Initialize the Unicode version of the secret (== password). */
297     /* This implicitly supports 8-bit ISO8859/1 characters. */
298     BZERO(unicodePassword, sizeof(unicodePassword));
299     for (i = 0; i < secret_len; i++)
300 	unicodePassword[i * 2] = (u_char)secret[i];
301 
302     MD4Init(&md4Context);
303     MD4Update(&md4Context, unicodePassword, mdlen);
304 
305     MD4Final(hash, &md4Context); 	/* Tell MD4 we're done */
306 
307     ChallengeResponse(rchallenge, hash, response->NTResp);
308 }
309 
310 #ifdef MSLANMAN
311 static u_char *StdText = (u_char *)"KGS!@#$%"; /* key from rasapi32.dll */
312 
313 static void
ChapMS_LANMan(rchallenge,secret,secret_len,response)314 ChapMS_LANMan(rchallenge, secret, secret_len, response)
315     u_char *rchallenge;
316     char *secret;
317     int secret_len;
318     MS_ChapResponse	*response;
319 {
320     int			i;
321     u_char		UcasePassword[MAX_NT_PASSWORD]; /* max is actually 14 */
322     u_char		PasswordHash[MD4_SIGNATURE_SIZE];
323 
324     /* LANMan password is case insensitive */
325     BZERO(UcasePassword, sizeof(UcasePassword));
326     for (i = 0; i < secret_len; i++)
327 	UcasePassword[i] = (u_char)(
328 	    islower(secret[i]) ? toupper(secret[i]) : secret[i]);
329     DesEncrypt( StdText, UcasePassword + 0, PasswordHash + 0 );
330     DesEncrypt( StdText, UcasePassword + 7, PasswordHash + 8 );
331     ChallengeResponse(rchallenge, PasswordHash, response->LANManResp);
332 }
333 #endif
334 
335 void
ChapMS(cstate,rchallenge,rchallenge_len,secret,secret_len)336 ChapMS(cstate, rchallenge, rchallenge_len, secret, secret_len)
337     chap_state *cstate;
338     u_char *rchallenge;
339     int rchallenge_len;
340     char *secret;
341     int secret_len;
342 {
343     MS_ChapResponse	response;
344 
345     if (rchallenge_len < 8) {
346 	    cstate->resp_length = 0;
347 	    return;
348     }
349 
350 #if 0
351     CHAPDEBUG((LOG_INFO, "ChapMS: secret is '%.*s'", secret_len, secret));
352 #endif
353     BZERO(&response, sizeof(response));
354 
355     /* Calculate both always */
356     ChapMS_NT(rchallenge, secret, secret_len, &response);
357 
358 #ifdef MSLANMAN
359     ChapMS_LANMan(rchallenge, secret, secret_len, &response);
360 
361     /* prefered method is set by option  */
362     response.UseNT = !ms_lanman;
363 #else
364     response.UseNT = 1;
365 #endif
366 
367     BCOPY(&response, cstate->response, MS_CHAP_RESPONSE_LEN);
368     cstate->resp_length = MS_CHAP_RESPONSE_LEN;
369 }
370 
371 static int
ChapMSStatus(cstate,flag)372 ChapMSStatus(cstate, flag)
373     chap_state *cstate;
374     int flag;
375 {
376     if (flag != 0) {
377 	cstate->stat_message = NULL;
378 	cstate->stat_length = 0;
379     } else {
380 	cstate->stat_message = "E=691 R=0 M=\"Authentication failed\"";
381 	cstate->stat_length = strlen(cstate->stat_message);
382     }
383     return (flag);
384 }
385 
386 int
ChapMSValidate(cstate,response,response_len,secret,secret_len)387 ChapMSValidate(cstate, response, response_len, secret, secret_len)
388     chap_state *cstate;
389     u_char *response;
390     int response_len;
391     char *secret;
392     int secret_len;
393 {
394     MS_ChapResponse ckresp;
395 
396     if (response_len < MS_CHAP_RESPONSE_LEN || cstate->chal_len < 8)
397 	return (0);
398 
399     BZERO(&ckresp, sizeof(ckresp));
400 
401     if (response[MS_CHAP_RESPONSE_LEN-1]) {
402 	ChapMS_NT(cstate->challenge, secret, secret_len, &ckresp);
403 	return (ChapMSStatus(cstate, memcmp(ckresp.NTResp, response+24,
404 	    24) == 0));
405     }
406 
407 #ifdef MSLANMAN
408     ChapMS_LANMan(cstate->challenge, secret, secret_len, &ckresp);
409     return (ChapMSStatus(cstate,
410 	memcmp(ckresp.LANManResp, response, 24) == 0));
411 #else
412     return (ChapMSStatus(cstate, 0));
413 #endif
414 }
415 #endif /* CHAPMS */
416 
417 #ifdef CHAPMSV2
418 static void
ChallengeHash(peerchallenge,authenticatorchallenge,username,challenge)419 ChallengeHash(peerchallenge, authenticatorchallenge, username, challenge)
420 u_char *peerchallenge, *authenticatorchallenge, *challenge;
421 char *username;
422 {
423     uint8_t digest[20];
424     SHA1_CTX sha1Context;
425     char *cp;
426 
427     SHA1Init(&sha1Context);
428     SHA1Update(&sha1Context, peerchallenge, 16);
429     SHA1Update(&sha1Context, authenticatorchallenge, 16);
430 
431     /*
432      * Only the user name (as presented by the peer and
433      * excluding any prepended domain name)
434      * is used as input to SHAUpdate().
435      */
436     if ((cp = strchr(username,'\\')) != NULL)
437 	username = cp;
438 
439     SHA1Update(&sha1Context, (uint8_t *)username, strlen(username));
440     SHA1Final(digest, &sha1Context);
441     BCOPY(digest, challenge, 8);
442 }
443 
444 static void
ChapMSv2_NT(username,rchallenge,secret,secret_len,response)445 ChapMSv2_NT(username, rchallenge, secret, secret_len, response)
446     char *username;
447     u_char *rchallenge;
448     char *secret;
449     int secret_len;
450     MS_Chapv2Response    *response;
451 {
452     int			i;
453 #if defined(__NetBSD__) || defined(HAVE_LIBMD)
454     /* NetBSD uses the libc md4 routines that take bytes instead of bits */
455     int			mdlen = secret_len * 2;
456 #else
457     int			mdlen = secret_len * 2 * 8;
458 #endif
459     MD4_CTX		md4Context;
460     u_char		hash[MD4_SIGNATURE_SIZE];
461     u_char		challenge[8];
462     u_char		unicodePassword[MAX_NT_PASSWORD * 2];
463 
464     /* Initialize the Unicode version of the secret (== password). */
465     /* This implicitly supports 8-bit ISO8859/1 characters. */
466     BZERO(unicodePassword, sizeof(unicodePassword));
467     for (i = 0; i < secret_len && i < MAX_NT_PASSWORD; i++)
468 	if ((unicodePassword[i * 2] = (u_char)secret[i]) == '\0')
469 	    break;
470 
471     ChallengeHash(response->PeerChallenge, rchallenge, username, challenge);
472 
473     MD4Init(&md4Context);
474     MD4Update(&md4Context, unicodePassword, mdlen);
475 
476     MD4Final(hash, &md4Context); 	/* Tell MD4 we're done */
477 
478     ChallengeResponse(challenge, hash, response->NTResp);
479 }
480 
481 void
ChapMSv2(cstate,rchallenge,rchallenge_len,secret,secret_len)482 ChapMSv2(cstate, rchallenge, rchallenge_len, secret, secret_len)
483     chap_state *cstate;
484     u_char *rchallenge;
485     int rchallenge_len;
486     char *secret;
487     int secret_len;
488 {
489     MS_Chapv2Response	response;
490     u_char *ptr;
491     int i;
492 
493     if (rchallenge_len < 8) {
494 	cstate->resp_length = 0;
495 	return;
496     }
497 
498     BZERO(&response, sizeof(response));
499 
500     ptr = response.PeerChallenge;
501     for (i = 0; i < 16; i++)
502 	*ptr++ = (u_char) (drand48() * 0xff);
503 
504     ChapMSv2_NT(cstate->resp_name, rchallenge, secret, secret_len, &response);
505 
506     BCOPY(&response, cstate->response, MS_CHAPV2_RESPONSE_LEN);
507     cstate->resp_length = MS_CHAPV2_RESPONSE_LEN;
508 }
509 
510 static void
ChapMSv2Success(cstate,msresp,authchall,rhostname,secret,secret_len)511 ChapMSv2Success(cstate, msresp, authchall, rhostname, secret, secret_len)
512     chap_state *cstate;
513     MS_Chapv2Response *msresp;
514     u_char *authchall;
515     char *rhostname, *secret;
516     int secret_len;
517 {
518     static const u_char Magic1[39] = "Magic server to client signing constant";
519     static const u_char Magic2[41] =
520 	"Pad to make it do more than one iteration";
521 #if defined(__NetBSD__) || defined(HAVE_LIBMD)
522     /* NetBSD uses the libc md4 routines that take bytes instead of bits */
523     int mdlen = 1;
524 #else
525     int mdlen = 8;
526 #endif
527     u_char unicodePassword[MAX_NT_PASSWORD * 2];
528     MD4_CTX md4Context;
529     u_char hash[MD4_SIGNATURE_SIZE];
530     u_char hashhash[MD4_SIGNATURE_SIZE];
531     SHA1_CTX sha1Context;
532     uint8_t digest[20];
533     u_char challenge[8];
534     char *cp;
535     static const char hexdig[] = "0123456789ABCDEF";
536     int i;
537 
538     /* Initialize the Unicode version of the secret (== password). */
539     /* This implicitly supports 8-bit ISO8859/1 characters. */
540     BZERO(unicodePassword, sizeof(unicodePassword));
541     for (i = 0; i < secret_len && i < MAX_NT_PASSWORD; i++)
542 	if ((unicodePassword[i * 2] = (u_char)secret[i]) == '\0')
543 	    break;
544 
545     /* Hash the password with MD4 */
546     MD4Init(&md4Context);
547     MD4Update(&md4Context, unicodePassword, secret_len * 2 * mdlen);
548     MD4Final(hash, &md4Context);
549 
550     /* Now hash the hash */
551     MD4Init(&md4Context);
552     MD4Update(&md4Context, hash, MD4_SIGNATURE_SIZE * mdlen);
553     MD4Final(hashhash, &md4Context);
554 
555     SHA1Init(&sha1Context);
556     SHA1Update(&sha1Context, hashhash, MD4_SIGNATURE_SIZE);
557     SHA1Update(&sha1Context, msresp->NTResp, sizeof (msresp->NTResp));
558     SHA1Update(&sha1Context, Magic1, 39);
559     SHA1Final(digest, &sha1Context);
560 
561     ChallengeHash(msresp->PeerChallenge, authchall, rhostname, challenge);
562 
563     SHA1Init(&sha1Context);
564     SHA1Update(&sha1Context, digest, 20);
565     SHA1Update(&sha1Context, challenge, 8);
566     SHA1Update(&sha1Context, Magic2, 41);
567     SHA1Final(digest, &sha1Context);
568 
569     cp = status_message;
570     *cp++ = 'S';
571     *cp++ = '=';
572     for (i = 0; i < 20; i++) {
573 	*cp++ = hexdig[digest[i]>>4];
574 	*cp++ = hexdig[digest[i]&15];
575     }
576     /*
577      * RFC 2759 says that a M=<string> greeting message is possible
578      * here.  It lies.  Any such greeting causes Windoze-98 to give
579      * error number 742, "Dial-Up Networking was unable to complete
580      * the connection.  The computer you're dialing in to does not
581      * support the data encryption requirements specified.  Please
582      * check your encryption settings in the properties of the
583      * connection.  If this problem persists, contact your network
584      * administrator."
585      */
586     *cp = '\0';
587 #if 0
588     slprintf(cp, sizeof (status_message) - (cp-status_message),
589 	"M=\"Welcome to %s.\"", hostname);
590 #endif
591     cstate->stat_message = status_message;
592     cstate->stat_length = strlen(status_message);
593 }
594 
595 int
ChapMSv2Validate(cstate,rhostname,response,response_len,secret,secret_len)596 ChapMSv2Validate(cstate, rhostname, response, response_len, secret, secret_len)
597     chap_state *cstate;
598     char *rhostname;
599     u_char *response;
600     int response_len;
601     char *secret;
602     int secret_len;
603 {
604     MS_Chapv2Response ckresp;
605 
606     if (response_len < MS_CHAPV2_RESPONSE_LEN ||
607 	/* response[MS_CHAPV2_RESPONSE_LEN-1] != 0 || */cstate->chal_len < 8) {
608 	cstate->stat_message = NULL;
609 	cstate->stat_length = 0;
610 	return 0;
611     }
612 
613     BZERO(&ckresp, sizeof(ckresp));
614 
615     BCOPY(response, ckresp.PeerChallenge, 16);
616 
617     ChapMSv2_NT(rhostname, cstate->challenge, secret, secret_len, &ckresp);
618     if (memcmp(ckresp.NTResp, response+24, 24) != 0) {
619 	cstate->stat_message = "E=691 R=0 C=11111111111111111111111111111111 V=3 M=\"Authentication failed\"";
620 	cstate->stat_length = strlen(cstate->stat_message);
621 	return (0);
622     }
623     ChapMSv2Success(cstate, (MS_Chapv2Response *)response, cstate->challenge,
624 	rhostname, secret, secret_len);
625     return (1);
626 }
627 #endif /* CHAPMSV2 */
628 
629 #endif /* CHAPMS or CHAPMSV2 */
630