2 * chap.c - support for (mutual) CHAP authentication.
4 * Copyright (C) 2004 Xiranet Communications GmbH <arne.redlich@xiranet.com>
5 * Copyright (C) 2002 - 2003 Ardis Technolgies <roman@ardistech.com>,
6 * Copyright (C) 2007 - 2009 Vladislav Bolkhovitin
7 * Copyright (C) 2007 - 2009 ID7 Ltd.
9 * and code taken from UNH iSCSI software:
10 * Copyright (C) 2001-2003 InterOperability Lab (IOL)
11 * University of New Hampshire (UNH)
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation, version 2
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * Heavily based on code from UNH iSCSI iscsid.c
30 #include <openssl/sha.h>
31 #include <openssl/md5.h>
35 #define HEX_FORMAT 0x01
36 #define BASE64_FORMAT 0x02
38 #define CHAP_DIGEST_ALG_MD5 5
39 #define CHAP_DIGEST_ALG_SHA1 7
41 #define CHAP_MD5_DIGEST_LEN 16
42 #define CHAP_SHA1_DIGEST_LEN 20
44 #define CHAP_INITIATOR_ERROR -1
45 #define CHAP_AUTH_ERROR -2
46 #define CHAP_TARGET_ERROR -3
48 #define CHAP_AUTH_STATE_START AUTH_STATE_START
49 #define CHAP_AUTH_STATE_CHALLENGE 1
50 #define CHAP_AUTH_STATE_RESPONSE 2
52 #define CHAP_INITIATOR_AUTH 0
53 #define CHAP_TARGET_AUTH 1
55 #define CHAP_CHALLENGE_MAX 50
57 static inline int decode_hex_digit(char c)
70 static void decode_hex_string(char *hex_string, u8 *intnum, int intlen)
75 j = strlen(hex_string);
79 intnum[j] = decode_hex_digit(*--ptr);
80 intnum[j] |= decode_hex_digit(*--ptr) << 4;
82 } while (ptr > hex_string);
89 /* Base64 decoding, taken from UNH-iSCSI "Base64codeToNumber()" */
90 static u8 decode_base64_digit(char base64)
100 if ((base64 >= 'A') && (base64 <= 'Z'))
102 else if ((base64 >= 'a') && (base64 <= 'z'))
103 return 26 + (base64 - 'a');
104 else if ((base64 >= '0') && (base64 <= '9'))
105 return 52 + (base64 - '0');
111 /* Base64 decoding, taken from UNH-iSCSI "Base64StringToInteger()" */
112 static void decode_base64_string(char *string, u8 *intnum, int int_len)
120 if ((string == NULL) || (intnum == NULL))
122 len = strlen(string);
129 while (count < len - 4) {
130 num[0] = decode_base64_digit(string[count]);
131 num[1] = decode_base64_digit(string[count + 1]);
132 num[2] = decode_base64_digit(string[count + 2]);
133 num[3] = decode_base64_digit(string[count + 3]);
134 if ((num[0] == 65) || (num[1] == 65) || (num[2] == 65) || (num[3] == 65))
138 (num[0] << 18) | (num[1] << 12) | (num[2] << 6) | num[3];
139 intnum[intptr] = (octets & 0xFF0000) >> 16;
140 intnum[intptr + 1] = (octets & 0x00FF00) >> 8;
141 intnum[intptr + 2] = octets & 0x0000FF;
144 num[0] = decode_base64_digit(string[count]);
145 num[1] = decode_base64_digit(string[count + 1]);
146 num[2] = decode_base64_digit(string[count + 2]);
147 num[3] = decode_base64_digit(string[count + 3]);
148 if ((num[0] == 64) || (num[1] == 64))
153 intnum[intptr] = (num[0] << 2) | (num[1] >> 4);
154 } else if (num[3] == 64) {
155 intnum[intptr] = (num[0] << 2) | (num[1] >> 4);
156 intnum[intptr + 1] = (num[1] << 4) | (num[2] >> 2);
159 (num[0] << 18) | (num[1] << 12) | (num[2] << 6) | num[3];
160 intnum[intptr] = (octets & 0xFF0000) >> 16;
161 intnum[intptr + 1] = (octets & 0x00FF00) >> 8;
162 intnum[intptr + 2] = octets & 0x0000FF;
166 static inline void encode_hex_string(u8 *intnum, long length, char *string)
172 for (i = 0; i < length; i++, strptr += 2)
173 sprintf(strptr, "%.2hhx", intnum[i]);
176 /* Base64 encoding, taken from UNH iSCSI "IntegerToBase64String()" */
177 static void encode_base64_string(u8 *intnum, long length, char *string)
179 int count, octets, strptr, delta;
180 static const char base64code[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G',
181 'H', 'I', 'J', 'K', 'L', 'M', 'N',
182 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
183 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
184 'c', 'd', 'e', 'f', 'g', 'h', 'i',
185 'j', 'k', 'l', 'm', 'n', 'o', 'p',
186 'q', 'r', 's', 't', 'u', 'v', 'w',
187 'x', 'y', 'z', '0', '1', '2', '3',
188 '4', '5', '6', '7', '8', '9', '+',
191 if ((!intnum) || (!string) || (!length))
198 while ((delta = (length - count)) > 2) {
199 octets = (intnum[count] << 16) | (intnum[count + 1] << 8) | intnum[count + 2];
200 string[strptr] = base64code[(octets & 0xfc0000) >> 18];
201 string[strptr + 1] = base64code[(octets & 0x03f000) >> 12];
202 string[strptr + 2] = base64code[(octets & 0x000fc0) >> 6];
203 string[strptr + 3] = base64code[octets & 0x00003f];
208 string[strptr] = base64code[(intnum[count] & 0xfc) >> 2];
209 string[strptr + 1] = base64code[(intnum[count] & 0x03) << 4];
210 string[strptr + 2] = base64code[64];
211 string[strptr + 3] = base64code[64];
213 } else if (delta == 2) {
214 string[strptr] = base64code[(intnum[count] & 0xfc) >> 2];
215 string[strptr + 1] = base64code[((intnum[count] & 0x03) << 4) | ((intnum[count + 1] & 0xf0) >> 4)];
216 string[strptr + 2] = base64code[(intnum[count + 1] & 0x0f) << 2];
217 string[strptr + 3] = base64code[64];
220 string[strptr] = '\0';
223 static inline int chap_check_encoding_format(char *encoded)
229 if ((strlen(encoded) < 3) || (encoded[0] != '0'))
232 if (encoded[1] == 'x' || encoded[1] == 'X')
233 encoding_fmt = HEX_FORMAT;
234 else if (encoded[1] == 'b' || encoded[1] == 'B')
235 encoding_fmt = BASE64_FORMAT;
242 static int chap_alloc_decode_buffer(char *encoded, u8 **decode_buf, int encoding_fmt)
250 if (encoding_fmt == HEX_FORMAT)
251 decode_len = (i - 1) / 2 + 1;
252 else if (encoding_fmt == BASE64_FORMAT) {
254 return CHAP_INITIATOR_ERROR;
256 decode_len = i / 4 * 3;
257 if (encoded[i + 1] == '=')
259 if (encoded[i] == '=')
264 return CHAP_INITIATOR_ERROR;
266 *decode_buf = malloc(decode_len);
268 return CHAP_TARGET_ERROR;
273 static int chap_decode_string(char *encoded, u8 *decode_buf, int buf_len, int encoding_fmt)
275 if (encoding_fmt == HEX_FORMAT) {
276 if ((strlen(encoded) - 2) > (2 * buf_len)) {
277 log_error("%s(%d) BUG? "
278 " buf[%d] !sufficient to decode string[%d]",
279 __FUNCTION__, __LINE__, buf_len, (int) strlen(encoded));
280 return CHAP_TARGET_ERROR;
282 decode_hex_string(encoded + 2, decode_buf, buf_len);
284 } else if (encoding_fmt == BASE64_FORMAT) {
285 if ((strlen(encoded) - 2) > ((buf_len - 1) / 3 + 1) * 4) {
286 log_error("%s(%d) BUG? "
287 " buf[%d] !sufficient to decode string[%d]",
288 __FUNCTION__, __LINE__, buf_len, (int) strlen(encoded));
289 return CHAP_TARGET_ERROR;
291 decode_base64_string(encoded + 2, decode_buf, buf_len);
294 return CHAP_INITIATOR_ERROR;
299 static inline void chap_encode_string(u8 *intnum, int buf_len, char *encode_buf, int encoding_fmt)
302 if (encoding_fmt == HEX_FORMAT) {
304 encode_hex_string(intnum, buf_len, encode_buf + 2);
305 } else if (encoding_fmt == BASE64_FORMAT) {
307 encode_base64_string(intnum, buf_len, encode_buf + 2);
311 static inline void chap_calc_digest_md5(char chap_id, char *secret, int secret_len, u8 *challenge, int challenge_len, u8 *digest)
316 MD5_Update(&ctx, &chap_id, 1);
317 MD5_Update(&ctx, secret, secret_len);
318 MD5_Update(&ctx, challenge, challenge_len);
319 MD5_Final(digest, &ctx);
322 static inline void chap_calc_digest_sha1(char chap_id, char *secret, int secret_len, u8 *challenge, int challenge_len, u8 *digest)
327 SHA1_Update(&ctx, &chap_id, 1);
328 SHA1_Update(&ctx, secret, secret_len);
329 SHA1_Update(&ctx, challenge, challenge_len);
330 SHA1_Final(digest, &ctx);
333 static int chap_initiator_auth_create_challenge(struct connection *conn)
336 char text[CHAP_CHALLENGE_MAX * 2 + 8];
340 value = text_key_find(conn, "CHAP_A");
342 return CHAP_INITIATOR_ERROR;
343 while ((p = strsep(&value, ","))) {
344 if (!strcmp(p, "5")) {
345 conn->auth.chap.digest_alg = CHAP_DIGEST_ALG_MD5;
346 conn->auth_state = CHAP_AUTH_STATE_CHALLENGE;
348 } else if (!strcmp(p, "7")) {
349 conn->auth.chap.digest_alg = CHAP_DIGEST_ALG_SHA1;
350 conn->auth_state = CHAP_AUTH_STATE_CHALLENGE;
355 return CHAP_INITIATOR_ERROR;
357 text_key_add(conn, "CHAP_A", p);
358 conn->auth.chap.id = ++chap_id;
359 sprintf(text, "%u", (unsigned char)conn->auth.chap.id);
360 text_key_add(conn, "CHAP_I", text);
363 * ToDo: does a random challenge length provide any benefits security-
364 * wise, or should we rather always use the max. allowed length of
365 * 1024 for the (unencoded) challenge?
367 conn->auth.chap.challenge_size = (rand() % (CHAP_CHALLENGE_MAX / 2)) + CHAP_CHALLENGE_MAX / 2;
369 conn->auth.chap.challenge = malloc(conn->auth.chap.challenge_size);
370 if (!conn->auth.chap.challenge)
371 return CHAP_TARGET_ERROR;
376 for (i = 0; i < conn->auth.chap.challenge_size; i++) {
377 conn->auth.chap.challenge[i] = rand();
378 sprintf(p, "%.2hhx", conn->auth.chap.challenge[i]);
381 text_key_add(conn, "CHAP_C", text);
386 static int chap_initiator_auth_check_response(struct connection *conn)
389 u8 *his_digest = NULL, *our_digest = NULL;
390 int digest_len = 0, retval = 0, encoding_format;
391 char pass[ISCSI_NAME_LEN];
393 memset(pass, 0, sizeof(pass));
394 if (config_account_query(conn->tid, AUTH_DIR_INCOMING, pass, pass) < 0) {
395 log_warning("CHAP initiator auth.: "
396 "No CHAP credentials configured");
397 retval = CHAP_TARGET_ERROR;
401 if (!(value = text_key_find(conn, "CHAP_N"))) {
402 retval = CHAP_INITIATOR_ERROR;
406 conn->user = strdup(value);
407 if (conn->user == NULL) {
408 log_error("Unable to dublicate initiator's USER %s", value);
411 memset(pass, 0, sizeof(pass));
412 if (config_account_query(conn->tid, AUTH_DIR_INCOMING, value, pass) < 0) {
413 log_warning("CHAP initiator auth.: "
414 "No valid user/pass combination for initiator %s "
415 "found", conn->initiator);
416 retval = CHAP_AUTH_ERROR;
420 if (!(value = text_key_find(conn, "CHAP_R"))) {
421 retval = CHAP_INITIATOR_ERROR;
425 if ((encoding_format = chap_check_encoding_format(value)) < 0) {
426 retval = CHAP_INITIATOR_ERROR;
430 switch (conn->auth.chap.digest_alg) {
431 case CHAP_DIGEST_ALG_MD5:
432 digest_len = CHAP_MD5_DIGEST_LEN;
434 case CHAP_DIGEST_ALG_SHA1:
435 digest_len = CHAP_SHA1_DIGEST_LEN;
438 retval = CHAP_TARGET_ERROR;
442 if (!(his_digest = malloc(digest_len))) {
443 retval = CHAP_TARGET_ERROR;
446 if (!(our_digest = malloc(digest_len))) {
447 retval = CHAP_TARGET_ERROR;
451 if (chap_decode_string(value, his_digest, digest_len, encoding_format) < 0) {
452 retval = CHAP_INITIATOR_ERROR;
456 switch (conn->auth.chap.digest_alg) {
457 case CHAP_DIGEST_ALG_MD5:
458 chap_calc_digest_md5(conn->auth.chap.id, pass, strlen(pass),
459 conn->auth.chap.challenge,
460 conn->auth.chap.challenge_size,
463 case CHAP_DIGEST_ALG_SHA1:
464 chap_calc_digest_sha1(conn->auth.chap.id, pass, strlen(pass),
465 conn->auth.chap.challenge,
466 conn->auth.chap.challenge_size,
470 retval = CHAP_TARGET_ERROR;
474 if (memcmp(our_digest, his_digest, digest_len)) {
475 log_warning("CHAP initiator auth.: "
476 "authentication of %s failed (wrong secret!?)",
478 retval = CHAP_AUTH_ERROR;
482 conn->state = CHAP_AUTH_STATE_RESPONSE;
491 static int chap_target_auth_create_response(struct connection *conn)
493 char chap_id, *value, *response = NULL;
494 u8 *challenge = NULL, *digest = NULL;
495 int encoding_format, response_len;
496 int challenge_len = 0, digest_len = 0, retval = 0;
497 char pass[ISCSI_NAME_LEN], name[ISCSI_NAME_LEN];
499 if (!(value = text_key_find(conn, "CHAP_I"))) {
500 /* initiator doesn't want target auth!? */
501 conn->state = STATE_SECURITY_DONE;
505 chap_id = strtol(value, &value, 10);
507 memset(pass, 0, sizeof(pass));
508 memset(name, 0, sizeof(name));
509 if (config_account_query(conn->tid, AUTH_DIR_OUTGOING, name, pass) < 0) {
510 log_warning("CHAP target auth.: "
511 "no outgoing credentials configured%s",
512 conn->tid ? "." : " for discovery.");
513 retval = CHAP_AUTH_ERROR;
517 if (!(value = text_key_find(conn, "CHAP_C"))) {
518 log_warning("CHAP target auth.: "
519 "got no challenge from initiator %s",
521 retval = CHAP_INITIATOR_ERROR;
525 if ((encoding_format = chap_check_encoding_format(value)) < 0) {
526 retval = CHAP_INITIATOR_ERROR;
530 retval = chap_alloc_decode_buffer(value, &challenge, encoding_format);
533 else if (retval > 1024) {
534 log_warning("CHAP target auth.: "
535 "initiator %s sent challenge of invalid length %d",
536 conn->initiator, challenge_len);
537 retval = CHAP_INITIATOR_ERROR;
541 challenge_len = retval;
544 switch (conn->auth.chap.digest_alg) {
545 case CHAP_DIGEST_ALG_MD5:
546 digest_len = CHAP_MD5_DIGEST_LEN;
548 case CHAP_DIGEST_ALG_SHA1:
549 digest_len = CHAP_SHA1_DIGEST_LEN;
552 retval = CHAP_TARGET_ERROR;
556 if (encoding_format == HEX_FORMAT)
557 response_len = 2 * digest_len;
559 response_len = ((digest_len - 1) / 3 + 1) * 4;
560 //"0x" / "0b" and "\0":
563 if (!(digest = malloc(digest_len))) {
564 retval = CHAP_TARGET_ERROR;
567 if (!(response = malloc(response_len))) {
568 retval = CHAP_TARGET_ERROR;
572 if (chap_decode_string(value, challenge, challenge_len, encoding_format) < 0) {
573 retval = CHAP_INITIATOR_ERROR;
577 /* RFC 3720, 8.2.1: CHAP challenges MUST NOT be reused */
578 if (challenge_len == conn->auth.chap.challenge_size) {
579 if (!memcmp(challenge, conn->auth.chap.challenge,
581 /* ToDo: RFC 3720 demands to close TCP conn */
582 log_warning("CHAP target auth.: "
583 "initiator %s reflected our challenge",
585 retval = CHAP_INITIATOR_ERROR;
590 switch (conn->auth.chap.digest_alg) {
591 case CHAP_DIGEST_ALG_MD5:
592 chap_calc_digest_md5(chap_id, pass, strlen(pass),
593 challenge, challenge_len, digest);
595 case CHAP_DIGEST_ALG_SHA1:
596 chap_calc_digest_sha1(chap_id, pass, strlen(pass),
597 challenge, challenge_len, digest);
600 retval = CHAP_TARGET_ERROR;
604 memset(response, 0x0, response_len);
605 chap_encode_string(digest, digest_len, response, encoding_format);
606 text_key_add(conn, "CHAP_N", name);
607 text_key_add(conn, "CHAP_R", response);
609 conn->state = STATE_SECURITY_DONE;
620 int cmnd_exec_auth_chap(struct connection *conn)
624 switch(conn->auth_state) {
625 case CHAP_AUTH_STATE_START:
626 res = chap_initiator_auth_create_challenge(conn);
628 case CHAP_AUTH_STATE_CHALLENGE:
629 res = chap_initiator_auth_check_response(conn);
633 case CHAP_AUTH_STATE_RESPONSE:
634 res = chap_target_auth_create_response(conn);
637 log_error("%s(%d): BUG. unknown conn->auth_state %d",
638 __FUNCTION__, __LINE__, conn->auth_state);
639 res = CHAP_TARGET_ERROR;