a2974cd8fa4193e7dc43be9686a354c8bcfbcb45
[mirror/scst/.git] / iscsi-scst / usr / chap.c
1 /*
2  *  chap.c - support for (mutual) CHAP authentication.
3  *
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.
8  *
9  *  and code taken from UNH iSCSI software:
10  *  Copyright (C) 2001-2003 InterOperability Lab (IOL)
11  *  University of New Hampshire (UNH)
12  *  Durham, NH 03824
13  *
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
17  *  of the License.
18  *
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.
23  *
24  *  Heavily based on code from UNH iSCSI iscsid.c
25  */
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <openssl/sha.h>
31 #include <openssl/md5.h>
32
33 #include "iscsid.h"
34
35 #define HEX_FORMAT    0x01
36 #define BASE64_FORMAT 0x02
37
38 #define CHAP_DIGEST_ALG_MD5   5
39 #define CHAP_DIGEST_ALG_SHA1  7
40
41 #define CHAP_MD5_DIGEST_LEN  16
42 #define CHAP_SHA1_DIGEST_LEN 20
43
44 #define CHAP_INITIATOR_ERROR -1
45 #define CHAP_AUTH_ERROR      -2
46 #define CHAP_TARGET_ERROR    -3
47
48 #define CHAP_AUTH_STATE_START     AUTH_STATE_START
49 #define CHAP_AUTH_STATE_CHALLENGE 1
50 #define CHAP_AUTH_STATE_RESPONSE  2
51
52 #define CHAP_INITIATOR_AUTH 0
53 #define CHAP_TARGET_AUTH    1
54
55 #define CHAP_CHALLENGE_MAX      50
56
57 static inline int decode_hex_digit(char c)
58 {
59         switch (c) {
60         case '0' ... '9':
61                 return c - '0';
62         case 'a' ... 'f':
63                 return c - 'a' + 10;
64         case 'A' ... 'F':
65                 return c - 'A' + 10;
66         }
67         return 0;
68 }
69
70 static void decode_hex_string(char *hex_string, u8 *intnum, int intlen)
71 {
72         char *ptr;
73         int j;
74
75         j = strlen(hex_string);
76         ptr = hex_string + j;
77         j = --intlen;
78         do {
79                 intnum[j] = decode_hex_digit(*--ptr);
80                 intnum[j] |= decode_hex_digit(*--ptr) << 4;
81                 j--;
82         } while (ptr > hex_string);
83
84         while (j >= 0)
85                 intnum[j--] = 0;
86 }
87
88
89 /* Base64 decoding, taken from UNH-iSCSI "Base64codeToNumber()" */
90 static u8 decode_base64_digit(char base64)
91 {
92         switch (base64) {
93         case '=':
94                 return 64;
95         case '/':
96                 return 63;
97         case '+':
98                 return 62;
99         default:
100                 if ((base64 >= 'A') && (base64 <= 'Z'))
101                         return base64 - 'A';
102                 else if ((base64 >= 'a') && (base64 <= 'z'))
103                         return 26 + (base64 - 'a');
104                 else if ((base64 >= '0') && (base64 <= '9'))
105                         return 52 + (base64 - '0');
106                 else
107                         return -1;
108         }
109 }
110
111 /* Base64 decoding, taken from UNH-iSCSI "Base64StringToInteger()" */
112 static void decode_base64_string(char *string, u8 *intnum, int int_len)
113 {
114         int len;
115         int count;
116         int intptr;
117         u8 num[4];
118         int octets;
119
120         if ((string == NULL) || (intnum == NULL))
121                 return;
122         len = strlen(string);
123         if (len == 0)
124                 return;
125         if ((len % 4) != 0)
126                 return;
127         count = 0;
128         intptr = 0;
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))
135                         return;
136                 count += 4;
137                 octets =
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;
142                 intptr += 3;
143         }
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))
149                 return;
150         if (num[2] == 64) {
151                 if (num[3] != 64)
152                         return;
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);
157         } else {
158                 octets =
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;
163         }
164 }
165
166 static inline void encode_hex_string(u8 *intnum, long length, char *string)
167 {
168         int i;
169         char *strptr;
170
171         strptr = string;
172         for (i = 0; i < length; i++, strptr += 2)
173                         sprintf(strptr, "%.2hhx", intnum[i]);
174 }
175
176 /* Base64 encoding, taken from UNH iSCSI "IntegerToBase64String()" */
177 static void encode_base64_string(u8 *intnum, long length, char *string)
178 {
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', '+',
189                                            '/', '=' };
190
191         if ((!intnum) || (!string) || (!length))
192                 return;
193
194         count = 0;
195         octets = 0;
196         strptr = 0;
197
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];
204                 count += 3;
205                 strptr += 4;
206         }
207         if (delta == 1) {
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];
212                 strptr += 4;
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];
218                 strptr += 4;
219         }
220         string[strptr] = '\0';
221 }
222
223 static inline int chap_check_encoding_format(char *encoded)
224 {
225         int encoding_fmt;
226
227         if (!encoded)
228                 return -1;
229         if ((strlen(encoded) < 3) || (encoded[0] != '0'))
230                 return -1;
231
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;
236         else
237                 return -1;
238
239         return encoding_fmt;
240 }
241
242 static int chap_alloc_decode_buffer(char *encoded, u8 **decode_buf, int encoding_fmt)
243 {
244         int i;
245         int decode_len = 0;
246
247         i = strlen(encoded);
248         i -= 2;
249
250         if (encoding_fmt == HEX_FORMAT)
251                 decode_len = (i - 1) / 2 + 1;
252         else if (encoding_fmt == BASE64_FORMAT) {
253                 if (i % 4)
254                         return CHAP_INITIATOR_ERROR;
255
256                 decode_len =  i / 4 * 3;
257                 if (encoded[i + 1] == '=')
258                         decode_len--;
259                 if (encoded[i] == '=')
260                         decode_len--;
261         }
262
263         if (!decode_len)
264                 return CHAP_INITIATOR_ERROR;
265
266         *decode_buf = malloc(decode_len);
267         if (!*decode_buf)
268                 return CHAP_TARGET_ERROR;
269
270         return decode_len;
271 }
272
273 static int chap_decode_string(char *encoded, u8 *decode_buf, int buf_len, int encoding_fmt)
274 {
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;
281                 }
282                 decode_hex_string(encoded + 2, decode_buf, buf_len);
283
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;
290                 }
291                 decode_base64_string(encoded + 2, decode_buf, buf_len);
292
293         } else
294                 return CHAP_INITIATOR_ERROR;
295
296         return 0;
297 }
298
299 static inline void chap_encode_string(u8 *intnum, int buf_len, char *encode_buf, int encoding_fmt)
300 {
301         encode_buf[0] = '0';
302         if (encoding_fmt == HEX_FORMAT) {
303                 encode_buf[1] = 'x';
304                 encode_hex_string(intnum, buf_len, encode_buf + 2);
305         } else if (encoding_fmt == BASE64_FORMAT) {
306                 encode_buf[1] = 'b';
307                 encode_base64_string(intnum, buf_len, encode_buf + 2);
308         }
309 }
310
311 static inline void chap_calc_digest_md5(char chap_id, char *secret, int secret_len, u8 *challenge, int challenge_len, u8 *digest)
312 {
313         MD5_CTX ctx;
314
315         MD5_Init(&ctx);
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);
320 }
321
322 static inline void chap_calc_digest_sha1(char chap_id, char *secret, int secret_len, u8 *challenge, int challenge_len, u8 *digest)
323 {
324         SHA_CTX ctx;
325
326         SHA1_Init(&ctx);
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);
331 }
332
333 static int chap_initiator_auth_create_challenge(struct connection *conn)
334 {
335         char *value, *p;
336         char text[CHAP_CHALLENGE_MAX * 2 + 8];
337         static int chap_id;
338         int i;
339
340         value = text_key_find(conn, "CHAP_A");
341         if (!value)
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;
347                         break;
348                 } else if (!strcmp(p, "7")) {
349                         conn->auth.chap.digest_alg = CHAP_DIGEST_ALG_SHA1;
350                         conn->auth_state = CHAP_AUTH_STATE_CHALLENGE;
351                         break;
352                 }
353         }
354         if (!p)
355                 return CHAP_INITIATOR_ERROR;
356
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);
361
362         /*
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?
366          */
367         conn->auth.chap.challenge_size = (rand() % (CHAP_CHALLENGE_MAX / 2)) + CHAP_CHALLENGE_MAX / 2;
368
369         conn->auth.chap.challenge = malloc(conn->auth.chap.challenge_size);
370         if (!conn->auth.chap.challenge)
371                 return CHAP_TARGET_ERROR;
372
373         p = text;
374         strcpy(p, "0x");
375         p += 2;
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]);
379                 p += 2;
380         }
381         text_key_add(conn, "CHAP_C",  text);
382
383         return 0;
384 }
385
386 static int chap_initiator_auth_check_response(struct connection *conn)
387 {
388         char *value;
389         u8 *his_digest = NULL, *our_digest = NULL;
390         int digest_len = 0, retval = 0, encoding_format;
391         char pass[ISCSI_NAME_LEN];
392
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;
398                 goto out;
399         }
400
401         if (!(value = text_key_find(conn, "CHAP_N"))) {
402                 retval = CHAP_INITIATOR_ERROR;
403                 goto out;
404         }
405
406         conn->user = strdup(value);
407         if (conn->user == NULL) {
408                 log_error("Unable to dublicate initiator's USER %s", value);
409         }
410
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;
417                 goto out;
418         }
419
420         if (!(value = text_key_find(conn, "CHAP_R"))) {
421                 retval = CHAP_INITIATOR_ERROR;
422                 goto out;
423         }
424
425         if ((encoding_format = chap_check_encoding_format(value)) < 0) {
426                 retval = CHAP_INITIATOR_ERROR;
427                 goto out;
428         }
429
430         switch (conn->auth.chap.digest_alg) {
431         case CHAP_DIGEST_ALG_MD5:
432                 digest_len = CHAP_MD5_DIGEST_LEN;
433                 break;
434         case CHAP_DIGEST_ALG_SHA1:
435                 digest_len = CHAP_SHA1_DIGEST_LEN;
436                 break;
437         default:
438                 retval = CHAP_TARGET_ERROR;
439                 goto out;
440         }
441
442         if (!(his_digest = malloc(digest_len))) {
443                 retval = CHAP_TARGET_ERROR;
444                 goto out;
445         }
446         if (!(our_digest = malloc(digest_len))) {
447                 retval = CHAP_TARGET_ERROR;
448                 goto out;
449         }
450
451         if (chap_decode_string(value, his_digest, digest_len, encoding_format) < 0) {
452                 retval = CHAP_INITIATOR_ERROR;
453                 goto out;
454         }
455
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,
461                                      our_digest);
462                 break;
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,
467                                       our_digest);
468                 break;
469         default:
470                 retval = CHAP_TARGET_ERROR;
471                 goto out;
472         }
473
474         if (memcmp(our_digest, his_digest, digest_len)) {
475                 log_warning("CHAP initiator auth.: "
476                             "authentication of %s failed (wrong secret!?)",
477                             conn->initiator);
478                 retval = CHAP_AUTH_ERROR;
479                 goto out;
480         }
481
482         conn->state = CHAP_AUTH_STATE_RESPONSE;
483  out:
484         if (his_digest)
485                 free(his_digest);
486         if (our_digest)
487                 free(our_digest);
488         return retval;
489 }
490
491 static int chap_target_auth_create_response(struct connection *conn)
492 {
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];
498
499         if (!(value = text_key_find(conn, "CHAP_I"))) {
500                 /* initiator doesn't want target auth!? */
501                 conn->state = STATE_SECURITY_DONE;
502                 retval = 0;
503                 goto out;
504         }
505         chap_id = strtol(value, &value, 10);
506
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;
514                 goto out;
515         }
516
517         if (!(value = text_key_find(conn, "CHAP_C"))) {
518                 log_warning("CHAP target auth.: "
519                             "got no challenge from initiator %s",
520                             conn->initiator);
521                 retval = CHAP_INITIATOR_ERROR;
522                 goto out;
523         }
524
525         if ((encoding_format = chap_check_encoding_format(value)) < 0) {
526                 retval = CHAP_INITIATOR_ERROR;
527                 goto out;
528         }
529
530         retval = chap_alloc_decode_buffer(value, &challenge, encoding_format);
531         if (retval <= 0)
532                 goto out;
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;
538                 goto out;
539         }
540
541         challenge_len = retval;
542         retval = 0;
543
544         switch (conn->auth.chap.digest_alg) {
545         case CHAP_DIGEST_ALG_MD5:
546                 digest_len = CHAP_MD5_DIGEST_LEN;
547                 break;
548         case CHAP_DIGEST_ALG_SHA1:
549                 digest_len = CHAP_SHA1_DIGEST_LEN;
550                 break;
551         default:
552                 retval = CHAP_TARGET_ERROR;
553                 goto out;
554         }
555
556         if (encoding_format == HEX_FORMAT)
557                 response_len = 2 * digest_len;
558         else
559                 response_len = ((digest_len - 1) / 3 + 1) * 4;
560         //"0x" / "0b" and "\0":
561         response_len += 3;
562
563         if (!(digest = malloc(digest_len))) {
564                 retval = CHAP_TARGET_ERROR;
565                 goto out;
566         }
567         if (!(response = malloc(response_len))) {
568                 retval = CHAP_TARGET_ERROR;
569                 goto out;
570         }
571
572         if (chap_decode_string(value, challenge, challenge_len, encoding_format) < 0) {
573                 retval = CHAP_INITIATOR_ERROR;
574                 goto out;
575         }
576
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,
580                             challenge_len)) {
581                         /* ToDo: RFC 3720 demands to close TCP conn */
582                         log_warning("CHAP target auth.: "
583                                     "initiator %s reflected our challenge",
584                                     conn->initiator);
585                         retval = CHAP_INITIATOR_ERROR;
586                         goto out;
587                 }
588         }
589
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);
594                 break;
595         case CHAP_DIGEST_ALG_SHA1:
596                 chap_calc_digest_sha1(chap_id, pass, strlen(pass),
597                                       challenge, challenge_len, digest);
598                 break;
599         default:
600                 retval = CHAP_TARGET_ERROR;
601                 goto out;
602         }
603
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);
608
609         conn->state = STATE_SECURITY_DONE;
610  out:
611         if (challenge)
612                 free(challenge);
613         if (digest)
614                 free(digest);
615         if (response)
616                 free(response);
617         return retval;
618 }
619
620 int cmnd_exec_auth_chap(struct connection *conn)
621 {
622         int res;
623
624         switch(conn->auth_state) {
625         case CHAP_AUTH_STATE_START:
626                 res = chap_initiator_auth_create_challenge(conn);
627                 break;
628         case CHAP_AUTH_STATE_CHALLENGE:
629                 res = chap_initiator_auth_check_response(conn);
630                 if (res < 0)
631                         break;
632                 /* fall through */
633         case CHAP_AUTH_STATE_RESPONSE:
634                 res = chap_target_auth_create_response(conn);
635                 break;
636         default:
637                 log_error("%s(%d): BUG. unknown conn->auth_state %d",
638                           __FUNCTION__, __LINE__, conn->auth_state);
639                 res = CHAP_TARGET_ERROR;
640         }
641
642         return res;
643 }