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