2 * chap_ms.c - Microsoft MS-CHAP compatible implementation.
4 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
5 * Use is subject to license terms.
7 * Copyright (c) 1995 Eric Rosenquist, Strata Software Limited.
8 * http://www.strataware.com/
10 * All rights reserved.
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.
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.
27 * This module implements MS-CHAPv1 (RFC 2433) and MS-CHAPv2 (RFC 2759).
29 * Modifications by Lauri Pesonen / lpesonen@clinet.fi, april 1997
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.
36 * You should also use DOMAIN\\USERNAME as described in README.MSCHAP80
38 * Modifications by James Carlson / james.d.carlson@sun.com, June 1st, 2000.
40 * Added MS-CHAPv2 support.
43 #if defined(CHAPMS) || defined(CHAPMSV2)
49 #include <sys/types.h>
70 u_char LANManResp
[24];
72 u_char UseNT
; /* If 1, ignore the LANMan response field */
74 /* We use MS_CHAP_RESPONSE_LEN, rather than sizeof(MS_ChapResponse),
75 in case this struct gets padded. */
78 u_char PeerChallenge
[16];
81 u_char Flags
; /* Should be zero (Win98 sends 04) */
83 /* We use MS_CHAPV2_RESPONSE_LEN, rather than sizeof(MS_Chapv2Response),
84 in case this struct gets padded. */
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));
91 static void ChapMS_NT
__P((u_char
*, char *, int, MS_ChapResponse
*));
93 static void ChapMS_LANMan
__P((u_char
*, char *, int, MS_ChapResponse
*));
97 static void ChapMSv2_NT
__P((char *, u_char
*, char *, int,
98 MS_Chapv2Response
*));
102 static void Expand
__P((u_char
*, char *));
103 static void Collapse
__P((char *, u_char
*));
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 */
112 /* Specially-formatted Microsoft CHAP response message. */
113 static char status_message
[256];
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 */
122 u_char ZPasswordHash
[21];
124 BZERO(ZPasswordHash
, sizeof(ZPasswordHash
));
125 BCOPY(pwHash
, ZPasswordHash
, MD4_SIGNATURE_SIZE
);
128 dbglog("ChallengeResponse - ZPasswordHash %.*B",
129 sizeof(ZPasswordHash
), ZPasswordHash
);
132 DesEncrypt(challenge
, ZPasswordHash
+ 0, response
+ 0);
133 DesEncrypt(challenge
, ZPasswordHash
+ 7, response
+ 8);
134 DesEncrypt(challenge
, ZPasswordHash
+ 14, response
+ 16);
137 dbglog("ChallengeResponse - response %.24B", response
);
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 */
153 MakeKey(key
, des_key
);
155 Expand(des_key
, crypt_key
);
159 CHAPDEBUG((LOG_INFO
, "DesEncrypt: 8 octet input : %.8B", clear
));
162 Expand(clear
, des_input
);
163 encrypt(des_input
, 0);
164 Collapse(des_input
, cipher
);
167 CHAPDEBUG((LOG_INFO
, "DesEncrypt: 8 octet output: %.8B", cipher
));
171 #else /* USE_CRYPT */
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 */
180 des_key_schedule key_schedule
;
182 MakeKey(key
, des_key
);
184 des_set_key(&des_key
, key_schedule
);
187 CHAPDEBUG((LOG_INFO
, "DesEncrypt: 8 octet input : %.8B", clear
));
190 des_ecb_encrypt((des_cblock
*)clear
, (des_cblock
*)cipher
, key_schedule
, 1);
193 CHAPDEBUG((LOG_INFO
, "DesEncrypt: 8 octet output: %.8B", cipher
));
197 #endif /* USE_CRYPT */
200 static u_char
Get7Bits(input
, startBit
)
204 register unsigned int word
;
206 word
= (unsigned)input
[startBit
/ 8] << 8;
207 word
|= (unsigned)input
[startBit
/ 8 + 1];
209 word
>>= 15 - (startBit
% 8 + 7);
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()
220 static void Expand(in
, out
)
227 for(i
= 0; i
< 64; in
++){
229 for(j
= 7; j
>= 0; j
--)
230 *out
++ = (c
>> j
) & 01;
235 /* The inverse of Expand
237 static void Collapse(in
, out
)
245 for (i
= 0; i
< 64; i
+= 8, out
++) {
247 for (j
= 7; j
>= 0; j
--, in
++)
248 c
|= *(u_char
*)in
<< j
;
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 */
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);
268 des_set_odd_parity((des_cblock
*)des_key
);
272 CHAPDEBUG((LOG_INFO
, "MakeKey: 56-bit input : %.7B", key
));
273 CHAPDEBUG((LOG_INFO
, "MakeKey: 64-bit output: %.8B", des_key
));
279 ChapMS_NT(rchallenge
, secret
, secret_len
, response
)
283 MS_ChapResponse
*response
;
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;
290 int mdlen
= secret_len
* 2 * 8;
293 u_char hash
[MD4_SIGNATURE_SIZE
];
294 u_char unicodePassword
[MAX_NT_PASSWORD
* 2];
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
];
302 MD4Init(&md4Context
);
303 MD4Update(&md4Context
, unicodePassword
, mdlen
);
305 MD4Final(hash
, &md4Context
); /* Tell MD4 we're done */
307 ChallengeResponse(rchallenge
, hash
, response
->NTResp
);
311 static u_char
*StdText
= (u_char
*)"KGS!@#$%"; /* key from rasapi32.dll */
314 ChapMS_LANMan(rchallenge
, secret
, secret_len
, response
)
318 MS_ChapResponse
*response
;
321 u_char UcasePassword
[MAX_NT_PASSWORD
]; /* max is actually 14 */
322 u_char PasswordHash
[MD4_SIGNATURE_SIZE
];
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
);
336 ChapMS(cstate
, rchallenge
, rchallenge_len
, secret
, secret_len
)
343 MS_ChapResponse response
;
345 if (rchallenge_len
< 8) {
346 cstate
->resp_length
= 0;
351 CHAPDEBUG((LOG_INFO
, "ChapMS: secret is '%.*s'", secret_len
, secret
));
353 BZERO(&response
, sizeof(response
));
355 /* Calculate both always */
356 ChapMS_NT(rchallenge
, secret
, secret_len
, &response
);
359 ChapMS_LANMan(rchallenge
, secret
, secret_len
, &response
);
361 /* prefered method is set by option */
362 response
.UseNT
= !ms_lanman
;
367 BCOPY(&response
, cstate
->response
, MS_CHAP_RESPONSE_LEN
);
368 cstate
->resp_length
= MS_CHAP_RESPONSE_LEN
;
372 ChapMSStatus(cstate
, flag
)
377 cstate
->stat_message
= NULL
;
378 cstate
->stat_length
= 0;
380 cstate
->stat_message
= "E=691 R=0 M=\"Authentication failed\"";
381 cstate
->stat_length
= strlen(cstate
->stat_message
);
387 ChapMSValidate(cstate
, response
, response_len
, secret
, secret_len
)
394 MS_ChapResponse ckresp
;
396 if (response_len
< MS_CHAP_RESPONSE_LEN
|| cstate
->chal_len
< 8)
399 BZERO(&ckresp
, sizeof(ckresp
));
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,
408 ChapMS_LANMan(cstate
->challenge
, secret
, secret_len
, &ckresp
);
409 return (ChapMSStatus(cstate
,
410 memcmp(ckresp
.LANManResp
, response
, 24) == 0));
412 return (ChapMSStatus(cstate
, 0));
419 ChallengeHash(peerchallenge
, authenticatorchallenge
, username
, challenge
)
420 u_char
*peerchallenge
, *authenticatorchallenge
, *challenge
;
424 SHA1_CTX sha1Context
;
427 SHA1Init(&sha1Context
);
428 SHA1Update(&sha1Context
, peerchallenge
, 16);
429 SHA1Update(&sha1Context
, authenticatorchallenge
, 16);
432 * Only the user name (as presented by the peer and
433 * excluding any prepended domain name)
434 * is used as input to SHAUpdate().
436 if ((cp
= strchr(username
,'\\')) != NULL
)
439 SHA1Update(&sha1Context
, (uint8_t *)username
, strlen(username
));
440 SHA1Final(digest
, &sha1Context
);
441 BCOPY(digest
, challenge
, 8);
445 ChapMSv2_NT(username
, rchallenge
, secret
, secret_len
, response
)
450 MS_Chapv2Response
*response
;
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;
457 int mdlen
= secret_len
* 2 * 8;
460 u_char hash
[MD4_SIGNATURE_SIZE
];
462 u_char unicodePassword
[MAX_NT_PASSWORD
* 2];
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')
471 ChallengeHash(response
->PeerChallenge
, rchallenge
, username
, challenge
);
473 MD4Init(&md4Context
);
474 MD4Update(&md4Context
, unicodePassword
, mdlen
);
476 MD4Final(hash
, &md4Context
); /* Tell MD4 we're done */
478 ChallengeResponse(challenge
, hash
, response
->NTResp
);
482 ChapMSv2(cstate
, rchallenge
, rchallenge_len
, secret
, secret_len
)
489 MS_Chapv2Response response
;
493 if (rchallenge_len
< 8) {
494 cstate
->resp_length
= 0;
498 BZERO(&response
, sizeof(response
));
500 ptr
= response
.PeerChallenge
;
501 for (i
= 0; i
< 16; i
++)
502 *ptr
++ = (u_char
) (drand48() * 0xff);
504 ChapMSv2_NT(cstate
->resp_name
, rchallenge
, secret
, secret_len
, &response
);
506 BCOPY(&response
, cstate
->response
, MS_CHAPV2_RESPONSE_LEN
);
507 cstate
->resp_length
= MS_CHAPV2_RESPONSE_LEN
;
511 ChapMSv2Success(cstate
, msresp
, authchall
, rhostname
, secret
, secret_len
)
513 MS_Chapv2Response
*msresp
;
515 char *rhostname
, *secret
;
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 */
527 u_char unicodePassword
[MAX_NT_PASSWORD
* 2];
529 u_char hash
[MD4_SIGNATURE_SIZE
];
530 u_char hashhash
[MD4_SIGNATURE_SIZE
];
531 SHA1_CTX sha1Context
;
535 static const char hexdig
[] = "0123456789ABCDEF";
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')
545 /* Hash the password with MD4 */
546 MD4Init(&md4Context
);
547 MD4Update(&md4Context
, unicodePassword
, secret_len
* 2 * mdlen
);
548 MD4Final(hash
, &md4Context
);
550 /* Now hash the hash */
551 MD4Init(&md4Context
);
552 MD4Update(&md4Context
, hash
, MD4_SIGNATURE_SIZE
* mdlen
);
553 MD4Final(hashhash
, &md4Context
);
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
);
561 ChallengeHash(msresp
->PeerChallenge
, authchall
, rhostname
, challenge
);
563 SHA1Init(&sha1Context
);
564 SHA1Update(&sha1Context
, digest
, 20);
565 SHA1Update(&sha1Context
, challenge
, 8);
566 SHA1Update(&sha1Context
, Magic2
, 41);
567 SHA1Final(digest
, &sha1Context
);
572 for (i
= 0; i
< 20; i
++) {
573 *cp
++ = hexdig
[digest
[i
]>>4];
574 *cp
++ = hexdig
[digest
[i
]&15];
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
588 slprintf(cp
, sizeof (status_message
) - (cp
-status_message
),
589 "M=\"Welcome to %s.\"", hostname
);
591 cstate
->stat_message
= status_message
;
592 cstate
->stat_length
= strlen(status_message
);
596 ChapMSv2Validate(cstate
, rhostname
, response
, response_len
, secret
, secret_len
)
604 MS_Chapv2Response ckresp
;
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;
613 BZERO(&ckresp
, sizeof(ckresp
));
615 BCOPY(response
, ckresp
.PeerChallenge
, 16);
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
);
623 ChapMSv2Success(cstate
, (MS_Chapv2Response
*)response
, cstate
->challenge
,
624 rhostname
, secret
, secret_len
);
627 #endif /* CHAPMSV2 */
629 #endif /* CHAPMS or CHAPMSV2 */