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);
88 /* Base64 decoding, taken from UNH-iSCSI "Base64codeToNumber()" */
89 static u8 decode_base64_digit(char base64)
99 if ((base64 >= 'A') && (base64 <= 'Z'))
101 else if ((base64 >= 'a') && (base64 <= 'z'))
102 return 26 + (base64 - 'a');
103 else if ((base64 >= '0') && (base64 <= '9'))
104 return 52 + (base64 - '0');
110 /* Base64 decoding, taken from UNH-iSCSI "Base64StringToInteger()" */
111 static void decode_base64_string(char *string, u8 *intnum, int int_len)
119 if ((string == NULL) || (intnum == NULL))
121 len = strlen(string);
128 while (count < len - 4) {
129 num[0] = decode_base64_digit(string[count]);
130 num[1] = decode_base64_digit(string[count + 1]);
131 num[2] = decode_base64_digit(string[count + 2]);
132 num[3] = decode_base64_digit(string[count + 3]);
133 if ((num[0] == 65) || (num[1] == 65) || (num[2] == 65) || (num[3] == 65))
137 (num[0] << 18) | (num[1] << 12) | (num[2] << 6) | num[3];
138 intnum[intptr] = (octets & 0xFF0000) >> 16;
139 intnum[intptr + 1] = (octets & 0x00FF00) >> 8;
140 intnum[intptr + 2] = octets & 0x0000FF;
143 num[0] = decode_base64_digit(string[count]);
144 num[1] = decode_base64_digit(string[count + 1]);
145 num[2] = decode_base64_digit(string[count + 2]);
146 num[3] = decode_base64_digit(string[count + 3]);
147 if ((num[0] == 64) || (num[1] == 64))
152 intnum[intptr] = (num[0] << 2) | (num[1] >> 4);
153 } else if (num[3] == 64) {
154 intnum[intptr] = (num[0] << 2) | (num[1] >> 4);
155 intnum[intptr + 1] = (num[1] << 4) | (num[2] >> 2);
158 (num[0] << 18) | (num[1] << 12) | (num[2] << 6) | num[3];
159 intnum[intptr] = (octets & 0xFF0000) >> 16;
160 intnum[intptr + 1] = (octets & 0x00FF00) >> 8;
161 intnum[intptr + 2] = octets & 0x0000FF;
165 static inline void encode_hex_string(u8 *intnum, long length, char *string)
171 for (i = 0; i < length; i++, strptr += 2)
172 sprintf(strptr, "%.2hhx", intnum[i]);
175 /* Base64 encoding, taken from UNH iSCSI "IntegerToBase64String()" */
176 static void encode_base64_string(u8 *intnum, long length, char *string)
178 int count, octets, strptr, delta;
179 static const char base64code[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G',
180 'H', 'I', 'J', 'K', 'L', 'M', 'N',
181 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
182 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
183 'c', 'd', 'e', 'f', 'g', 'h', 'i',
184 'j', 'k', 'l', 'm', 'n', 'o', 'p',
185 'q', 'r', 's', 't', 'u', 'v', 'w',
186 'x', 'y', 'z', '0', '1', '2', '3',
187 '4', '5', '6', '7', '8', '9', '+',
190 if ((!intnum) || (!string) || (!length))
197 while ((delta = (length - count)) > 2) {
198 octets = (intnum[count] << 16) | (intnum[count + 1] << 8) | intnum[count + 2];
199 string[strptr] = base64code[(octets & 0xfc0000) >> 18];
200 string[strptr + 1] = base64code[(octets & 0x03f000) >> 12];
201 string[strptr + 2] = base64code[(octets & 0x000fc0) >> 6];
202 string[strptr + 3] = base64code[octets & 0x00003f];
207 string[strptr] = base64code[(intnum[count] & 0xfc) >> 2];
208 string[strptr + 1] = base64code[(intnum[count] & 0x03) << 4];
209 string[strptr + 2] = base64code[64];
210 string[strptr + 3] = base64code[64];
212 } else if (delta == 2) {
213 string[strptr] = base64code[(intnum[count] & 0xfc) >> 2];
214 string[strptr + 1] = base64code[((intnum[count] & 0x03) << 4) | ((intnum[count + 1] & 0xf0) >> 4)];
215 string[strptr + 2] = base64code[(intnum[count + 1] & 0x0f) << 2];
216 string[strptr + 3] = base64code[64];
219 string[strptr] = '\0';
222 static inline int chap_check_encoding_format(char *encoded)
228 if ((strlen(encoded) < 3) || (encoded[0] != '0'))
231 if (encoded[1] == 'x' || encoded[1] == 'X')
232 encoding_fmt = HEX_FORMAT;
233 else if (encoded[1] == 'b' || encoded[1] == 'B')
234 encoding_fmt = BASE64_FORMAT;
241 static int chap_alloc_decode_buffer(char *encoded, u8 **decode_buf, int encoding_fmt)
249 if (encoding_fmt == HEX_FORMAT)
250 decode_len = (i - 1) / 2 + 1;
251 else if (encoding_fmt == BASE64_FORMAT) {
253 return CHAP_INITIATOR_ERROR;
255 decode_len = i / 4 * 3;
256 if (encoded[i + 1] == '=')
258 if (encoded[i] == '=')
263 return CHAP_INITIATOR_ERROR;
265 *decode_buf = malloc(decode_len);
267 return CHAP_TARGET_ERROR;
272 static int chap_decode_string(char *encoded, u8 *decode_buf, int buf_len, int encoding_fmt)
274 if (encoding_fmt == HEX_FORMAT) {
275 if ((strlen(encoded) - 2) > (2 * buf_len)) {
276 log_error("%s(%d) BUG? "
277 " buf[%d] !sufficient to decode string[%d]",
278 __FUNCTION__, __LINE__, buf_len, (int) strlen(encoded));
279 return CHAP_TARGET_ERROR;
281 decode_hex_string(encoded + 2, decode_buf, buf_len);
283 } else if (encoding_fmt == BASE64_FORMAT) {
284 if ((strlen(encoded) - 2) > ((buf_len - 1) / 3 + 1) * 4) {
285 log_error("%s(%d) BUG? "
286 " buf[%d] !sufficient to decode string[%d]",
287 __FUNCTION__, __LINE__, buf_len, (int) strlen(encoded));
288 return CHAP_TARGET_ERROR;
290 decode_base64_string(encoded + 2, decode_buf, buf_len);
293 return CHAP_INITIATOR_ERROR;
298 static inline void chap_encode_string(u8 *intnum, int buf_len, char *encode_buf, int encoding_fmt)
301 if (encoding_fmt == HEX_FORMAT) {
303 encode_hex_string(intnum, buf_len, encode_buf + 2);
304 } else if (encoding_fmt == BASE64_FORMAT) {
306 encode_base64_string(intnum, buf_len, encode_buf + 2);
310 static inline void chap_calc_digest_md5(char chap_id, char *secret, int secret_len, u8 *challenge, int challenge_len, u8 *digest)
315 MD5_Update(&ctx, &chap_id, 1);
316 MD5_Update(&ctx, secret, secret_len);
317 MD5_Update(&ctx, challenge, challenge_len);
318 MD5_Final(digest, &ctx);
321 static inline void chap_calc_digest_sha1(char chap_id, char *secret, int secret_len, u8 *challenge, int challenge_len, u8 *digest)
326 SHA1_Update(&ctx, &chap_id, 1);
327 SHA1_Update(&ctx, secret, secret_len);
328 SHA1_Update(&ctx, challenge, challenge_len);
329 SHA1_Final(digest, &ctx);
332 static int chap_initiator_auth_create_challenge(struct connection *conn)
335 char text[CHAP_CHALLENGE_MAX * 2 + 8];
339 value = text_key_find(conn, "CHAP_A");
341 return CHAP_INITIATOR_ERROR;
342 while ((p = strsep(&value, ","))) {
343 if (!strcmp(p, "5")) {
344 conn->auth.chap.digest_alg = CHAP_DIGEST_ALG_MD5;
345 conn->auth_state = CHAP_AUTH_STATE_CHALLENGE;
347 } else if (!strcmp(p, "7")) {
348 conn->auth.chap.digest_alg = CHAP_DIGEST_ALG_SHA1;
349 conn->auth_state = CHAP_AUTH_STATE_CHALLENGE;
354 return CHAP_INITIATOR_ERROR;
356 text_key_add(conn, "CHAP_A", p);
357 conn->auth.chap.id = ++chap_id;
358 sprintf(text, "%u", (unsigned char)conn->auth.chap.id);
359 text_key_add(conn, "CHAP_I", text);
362 * ToDo: does a random challenge length provide any benefits security-
363 * wise, or should we rather always use the max. allowed length of
364 * 1024 for the (unencoded) challenge?
366 conn->auth.chap.challenge_size = (rand() % (CHAP_CHALLENGE_MAX / 2)) + CHAP_CHALLENGE_MAX / 2;
368 conn->auth.chap.challenge = malloc(conn->auth.chap.challenge_size);
369 if (!conn->auth.chap.challenge)
370 return CHAP_TARGET_ERROR;
375 for (i = 0; i < conn->auth.chap.challenge_size; i++) {
376 conn->auth.chap.challenge[i] = rand();
377 sprintf(p, "%.2hhx", conn->auth.chap.challenge[i]);
380 text_key_add(conn, "CHAP_C", text);
385 static int chap_initiator_auth_check_response(struct connection *conn)
388 u8 *his_digest = NULL, *our_digest = NULL;
389 int digest_len = 0, retval = 0, encoding_format;
390 char pass[ISCSI_NAME_LEN];
392 memset(pass, 0, sizeof(pass));
393 if (config_account_query(conn->tid, AUTH_DIR_INCOMING, pass, pass) < 0) {
394 log_warning("CHAP initiator auth.: "
395 "No CHAP credentials configured");
396 retval = CHAP_TARGET_ERROR;
400 if (!(value = text_key_find(conn, "CHAP_N"))) {
401 retval = CHAP_INITIATOR_ERROR;
405 conn->user = strdup(value);
406 if (conn->user == NULL) {
407 log_error("Unable to dublicate initiator's USER %s", value);
410 memset(pass, 0, sizeof(pass));
411 if (config_account_query(conn->tid, AUTH_DIR_INCOMING, value, pass) < 0) {
412 log_warning("CHAP initiator auth.: "
413 "No valid user/pass combination for initiator %s "
414 "found", conn->initiator);
415 retval = CHAP_AUTH_ERROR;
419 if (!(value = text_key_find(conn, "CHAP_R"))) {
420 retval = CHAP_INITIATOR_ERROR;
424 if ((encoding_format = chap_check_encoding_format(value)) < 0) {
425 retval = CHAP_INITIATOR_ERROR;
429 switch (conn->auth.chap.digest_alg) {
430 case CHAP_DIGEST_ALG_MD5:
431 digest_len = CHAP_MD5_DIGEST_LEN;
433 case CHAP_DIGEST_ALG_SHA1:
434 digest_len = CHAP_SHA1_DIGEST_LEN;
437 retval = CHAP_TARGET_ERROR;
441 if (!(his_digest = malloc(digest_len))) {
442 retval = CHAP_TARGET_ERROR;
445 if (!(our_digest = malloc(digest_len))) {
446 retval = CHAP_TARGET_ERROR;
450 if (chap_decode_string(value, his_digest, digest_len, encoding_format) < 0) {
451 retval = CHAP_INITIATOR_ERROR;
455 switch (conn->auth.chap.digest_alg) {
456 case CHAP_DIGEST_ALG_MD5:
457 chap_calc_digest_md5(conn->auth.chap.id, pass, strlen(pass),
458 conn->auth.chap.challenge,
459 conn->auth.chap.challenge_size,
462 case CHAP_DIGEST_ALG_SHA1:
463 chap_calc_digest_sha1(conn->auth.chap.id, pass, strlen(pass),
464 conn->auth.chap.challenge,
465 conn->auth.chap.challenge_size,
469 retval = CHAP_TARGET_ERROR;
473 if (memcmp(our_digest, his_digest, digest_len)) {
474 log_warning("CHAP initiator auth.: "
475 "authentication of %s failed (wrong secret!?)",
477 retval = CHAP_AUTH_ERROR;
481 conn->state = CHAP_AUTH_STATE_RESPONSE;
490 static int chap_target_auth_create_response(struct connection *conn)
492 char chap_id, *value, *response = NULL;
493 u8 *challenge = NULL, *digest = NULL;
494 int encoding_format, response_len;
495 int challenge_len = 0, digest_len = 0, retval = 0;
496 char pass[ISCSI_NAME_LEN], name[ISCSI_NAME_LEN];
498 if (!(value = text_key_find(conn, "CHAP_I"))) {
499 /* initiator doesn't want target auth!? */
500 conn->state = STATE_SECURITY_DONE;
504 chap_id = strtol(value, &value, 10);
506 memset(pass, 0, sizeof(pass));
507 memset(name, 0, sizeof(name));
508 if (config_account_query(conn->tid, AUTH_DIR_OUTGOING, name, pass) < 0) {
509 log_warning("CHAP target auth.: "
510 "no outgoing credentials configured%s",
511 conn->tid ? "." : " for discovery.");
512 retval = CHAP_AUTH_ERROR;
516 if (!(value = text_key_find(conn, "CHAP_C"))) {
517 log_warning("CHAP target auth.: "
518 "got no challenge from initiator %s",
520 retval = CHAP_INITIATOR_ERROR;
524 if ((encoding_format = chap_check_encoding_format(value)) < 0) {
525 retval = CHAP_INITIATOR_ERROR;
529 retval = chap_alloc_decode_buffer(value, &challenge, encoding_format);
532 else if (retval > 1024) {
533 log_warning("CHAP target auth.: "
534 "initiator %s sent challenge of invalid length %d",
535 conn->initiator, challenge_len);
536 retval = CHAP_INITIATOR_ERROR;
540 challenge_len = retval;
543 switch (conn->auth.chap.digest_alg) {
544 case CHAP_DIGEST_ALG_MD5:
545 digest_len = CHAP_MD5_DIGEST_LEN;
547 case CHAP_DIGEST_ALG_SHA1:
548 digest_len = CHAP_SHA1_DIGEST_LEN;
551 retval = CHAP_TARGET_ERROR;
555 if (encoding_format == HEX_FORMAT)
556 response_len = 2 * digest_len;
558 response_len = ((digest_len - 1) / 3 + 1) * 4;
559 //"0x" / "0b" and "\0":
562 if (!(digest = malloc(digest_len))) {
563 retval = CHAP_TARGET_ERROR;
566 if (!(response = malloc(response_len))) {
567 retval = CHAP_TARGET_ERROR;
571 if (chap_decode_string(value, challenge, challenge_len, encoding_format) < 0) {
572 retval = CHAP_INITIATOR_ERROR;
576 /* RFC 3720, 8.2.1: CHAP challenges MUST NOT be reused */
577 if (challenge_len == conn->auth.chap.challenge_size) {
578 if (!memcmp(challenge, conn->auth.chap.challenge,
580 /* ToDo: RFC 3720 demands to close TCP conn */
581 log_warning("CHAP target auth.: "
582 "initiator %s reflected our challenge",
584 retval = CHAP_INITIATOR_ERROR;
589 switch (conn->auth.chap.digest_alg) {
590 case CHAP_DIGEST_ALG_MD5:
591 chap_calc_digest_md5(chap_id, pass, strlen(pass),
592 challenge, challenge_len, digest);
594 case CHAP_DIGEST_ALG_SHA1:
595 chap_calc_digest_sha1(chap_id, pass, strlen(pass),
596 challenge, challenge_len, digest);
599 retval = CHAP_TARGET_ERROR;
603 memset(response, 0x0, response_len);
604 chap_encode_string(digest, digest_len, response, encoding_format);
605 text_key_add(conn, "CHAP_N", name);
606 text_key_add(conn, "CHAP_R", response);
608 conn->state = STATE_SECURITY_DONE;
619 int cmnd_exec_auth_chap(struct connection *conn)
623 switch(conn->auth_state) {
624 case CHAP_AUTH_STATE_START:
625 res = chap_initiator_auth_create_challenge(conn);
627 case CHAP_AUTH_STATE_CHALLENGE:
628 res = chap_initiator_auth_check_response(conn);
632 case CHAP_AUTH_STATE_RESPONSE:
633 res = chap_target_auth_create_response(conn);
636 log_error("%s(%d): BUG. unknown conn->auth_state %d",
637 __FUNCTION__, __LINE__, conn->auth_state);
638 res = CHAP_TARGET_ERROR;