Cosmetics.
[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 /* Base64 decoding, taken from UNH-iSCSI "Base64codeToNumber()" */
89 static u8 decode_base64_digit(char base64)
90 {
91         switch (base64) {
92         case '=':
93                 return 64;
94         case '/':
95                 return 63;
96         case '+':
97                 return 62;
98         default:
99                 if ((base64 >= 'A') && (base64 <= 'Z'))
100                         return base64 - 'A';
101                 else if ((base64 >= 'a') && (base64 <= 'z'))
102                         return 26 + (base64 - 'a');
103                 else if ((base64 >= '0') && (base64 <= '9'))
104                         return 52 + (base64 - '0');
105                 else
106                         return -1;
107         }
108 }
109
110 /* Base64 decoding, taken from UNH-iSCSI "Base64StringToInteger()" */
111 static void decode_base64_string(char *string, u8 *intnum, int int_len)
112 {
113         int len;
114         int count;
115         int intptr;
116         u8 num[4];
117         int octets;
118
119         if ((string == NULL) || (intnum == NULL))
120                 return;
121         len = strlen(string);
122         if (len == 0)
123                 return;
124         if ((len % 4) != 0)
125                 return;
126         count = 0;
127         intptr = 0;
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))
134                         return;
135                 count += 4;
136                 octets =
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;
141                 intptr += 3;
142         }
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))
148                 return;
149         if (num[2] == 64) {
150                 if (num[3] != 64)
151                         return;
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);
156         } else {
157                 octets =
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;
162         }
163 }
164
165 static inline void encode_hex_string(u8 *intnum, long length, char *string)
166 {
167         int i;
168         char *strptr;
169
170         strptr = string;
171         for (i = 0; i < length; i++, strptr += 2)
172                         sprintf(strptr, "%.2hhx", intnum[i]);
173 }
174
175 /* Base64 encoding, taken from UNH iSCSI "IntegerToBase64String()" */
176 static void encode_base64_string(u8 *intnum, long length, char *string)
177 {
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', '+',
188                                            '/', '=' };
189
190         if ((!intnum) || (!string) || (!length))
191                 return;
192
193         count = 0;
194         octets = 0;
195         strptr = 0;
196
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];
203                 count += 3;
204                 strptr += 4;
205         }
206         if (delta == 1) {
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];
211                 strptr += 4;
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];
217                 strptr += 4;
218         }
219         string[strptr] = '\0';
220 }
221
222 static inline int chap_check_encoding_format(char *encoded)
223 {
224         int encoding_fmt;
225
226         if (!encoded)
227                 return -1;
228         if ((strlen(encoded) < 3) || (encoded[0] != '0'))
229                 return -1;
230
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;
235         else
236                 return -1;
237
238         return encoding_fmt;
239 }
240
241 static int chap_alloc_decode_buffer(char *encoded, u8 **decode_buf, int encoding_fmt)
242 {
243         int i;
244         int decode_len = 0;
245
246         i = strlen(encoded);
247         i -= 2;
248
249         if (encoding_fmt == HEX_FORMAT)
250                 decode_len = (i - 1) / 2 + 1;
251         else if (encoding_fmt == BASE64_FORMAT) {
252                 if (i % 4)
253                         return CHAP_INITIATOR_ERROR;
254
255                 decode_len =  i / 4 * 3;
256                 if (encoded[i + 1] == '=')
257                         decode_len--;
258                 if (encoded[i] == '=')
259                         decode_len--;
260         }
261
262         if (!decode_len)
263                 return CHAP_INITIATOR_ERROR;
264
265         *decode_buf = malloc(decode_len);
266         if (!*decode_buf)
267                 return CHAP_TARGET_ERROR;
268
269         return decode_len;
270 }
271
272 static int chap_decode_string(char *encoded, u8 *decode_buf, int buf_len, int encoding_fmt)
273 {
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;
280                 }
281                 decode_hex_string(encoded + 2, decode_buf, buf_len);
282
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;
289                 }
290                 decode_base64_string(encoded + 2, decode_buf, buf_len);
291
292         } else
293                 return CHAP_INITIATOR_ERROR;
294
295         return 0;
296 }
297
298 static inline void chap_encode_string(u8 *intnum, int buf_len, char *encode_buf, int encoding_fmt)
299 {
300         encode_buf[0] = '0';
301         if (encoding_fmt == HEX_FORMAT) {
302                 encode_buf[1] = 'x';
303                 encode_hex_string(intnum, buf_len, encode_buf + 2);
304         } else if (encoding_fmt == BASE64_FORMAT) {
305                 encode_buf[1] = 'b';
306                 encode_base64_string(intnum, buf_len, encode_buf + 2);
307         }
308 }
309
310 static inline void chap_calc_digest_md5(char chap_id, char *secret, int secret_len, u8 *challenge, int challenge_len, u8 *digest)
311 {
312         MD5_CTX ctx;
313
314         MD5_Init(&ctx);
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);
319 }
320
321 static inline void chap_calc_digest_sha1(char chap_id, char *secret, int secret_len, u8 *challenge, int challenge_len, u8 *digest)
322 {
323         SHA_CTX ctx;
324
325         SHA1_Init(&ctx);
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);
330 }
331
332 static int chap_initiator_auth_create_challenge(struct connection *conn)
333 {
334         char *value, *p;
335         char text[CHAP_CHALLENGE_MAX * 2 + 8];
336         static int chap_id;
337         int i;
338
339         value = text_key_find(conn, "CHAP_A");
340         if (!value)
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;
346                         break;
347                 } else if (!strcmp(p, "7")) {
348                         conn->auth.chap.digest_alg = CHAP_DIGEST_ALG_SHA1;
349                         conn->auth_state = CHAP_AUTH_STATE_CHALLENGE;
350                         break;
351                 }
352         }
353         if (!p)
354                 return CHAP_INITIATOR_ERROR;
355
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);
360
361         /*
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?
365          */
366         conn->auth.chap.challenge_size = (rand() % (CHAP_CHALLENGE_MAX / 2)) + CHAP_CHALLENGE_MAX / 2;
367
368         conn->auth.chap.challenge = malloc(conn->auth.chap.challenge_size);
369         if (!conn->auth.chap.challenge)
370                 return CHAP_TARGET_ERROR;
371
372         p = text;
373         strcpy(p, "0x");
374         p += 2;
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]);
378                 p += 2;
379         }
380         text_key_add(conn, "CHAP_C",  text);
381
382         return 0;
383 }
384
385 static int chap_initiator_auth_check_response(struct connection *conn)
386 {
387         char *value;
388         u8 *his_digest = NULL, *our_digest = NULL;
389         int digest_len = 0, retval = 0, encoding_format;
390         char pass[ISCSI_NAME_LEN];
391
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;
397                 goto out;
398         }
399
400         if (!(value = text_key_find(conn, "CHAP_N"))) {
401                 retval = CHAP_INITIATOR_ERROR;
402                 goto out;
403         }
404
405         conn->user = strdup(value);
406         if (conn->user == NULL) {
407                 log_error("Unable to dublicate initiator's USER %s", value);
408         }
409
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;
416                 goto out;
417         }
418
419         if (!(value = text_key_find(conn, "CHAP_R"))) {
420                 retval = CHAP_INITIATOR_ERROR;
421                 goto out;
422         }
423
424         if ((encoding_format = chap_check_encoding_format(value)) < 0) {
425                 retval = CHAP_INITIATOR_ERROR;
426                 goto out;
427         }
428
429         switch (conn->auth.chap.digest_alg) {
430         case CHAP_DIGEST_ALG_MD5:
431                 digest_len = CHAP_MD5_DIGEST_LEN;
432                 break;
433         case CHAP_DIGEST_ALG_SHA1:
434                 digest_len = CHAP_SHA1_DIGEST_LEN;
435                 break;
436         default:
437                 retval = CHAP_TARGET_ERROR;
438                 goto out;
439         }
440
441         if (!(his_digest = malloc(digest_len))) {
442                 retval = CHAP_TARGET_ERROR;
443                 goto out;
444         }
445         if (!(our_digest = malloc(digest_len))) {
446                 retval = CHAP_TARGET_ERROR;
447                 goto out;
448         }
449
450         if (chap_decode_string(value, his_digest, digest_len, encoding_format) < 0) {
451                 retval = CHAP_INITIATOR_ERROR;
452                 goto out;
453         }
454
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,
460                                      our_digest);
461                 break;
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,
466                                       our_digest);
467                 break;
468         default:
469                 retval = CHAP_TARGET_ERROR;
470                 goto out;
471         }
472
473         if (memcmp(our_digest, his_digest, digest_len)) {
474                 log_warning("CHAP initiator auth.: "
475                             "authentication of %s failed (wrong secret!?)",
476                             conn->initiator);
477                 retval = CHAP_AUTH_ERROR;
478                 goto out;
479         }
480
481         conn->state = CHAP_AUTH_STATE_RESPONSE;
482  out:
483         if (his_digest)
484                 free(his_digest);
485         if (our_digest)
486                 free(our_digest);
487         return retval;
488 }
489
490 static int chap_target_auth_create_response(struct connection *conn)
491 {
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];
497
498         if (!(value = text_key_find(conn, "CHAP_I"))) {
499                 /* initiator doesn't want target auth!? */
500                 conn->state = STATE_SECURITY_DONE;
501                 retval = 0;
502                 goto out;
503         }
504         chap_id = strtol(value, &value, 10);
505
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;
513                 goto out;
514         }
515
516         if (!(value = text_key_find(conn, "CHAP_C"))) {
517                 log_warning("CHAP target auth.: "
518                             "got no challenge from initiator %s",
519                             conn->initiator);
520                 retval = CHAP_INITIATOR_ERROR;
521                 goto out;
522         }
523
524         if ((encoding_format = chap_check_encoding_format(value)) < 0) {
525                 retval = CHAP_INITIATOR_ERROR;
526                 goto out;
527         }
528
529         retval = chap_alloc_decode_buffer(value, &challenge, encoding_format);
530         if (retval <= 0)
531                 goto out;
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;
537                 goto out;
538         }
539
540         challenge_len = retval;
541         retval = 0;
542
543         switch (conn->auth.chap.digest_alg) {
544         case CHAP_DIGEST_ALG_MD5:
545                 digest_len = CHAP_MD5_DIGEST_LEN;
546                 break;
547         case CHAP_DIGEST_ALG_SHA1:
548                 digest_len = CHAP_SHA1_DIGEST_LEN;
549                 break;
550         default:
551                 retval = CHAP_TARGET_ERROR;
552                 goto out;
553         }
554
555         if (encoding_format == HEX_FORMAT)
556                 response_len = 2 * digest_len;
557         else
558                 response_len = ((digest_len - 1) / 3 + 1) * 4;
559         //"0x" / "0b" and "\0":
560         response_len += 3;
561
562         if (!(digest = malloc(digest_len))) {
563                 retval = CHAP_TARGET_ERROR;
564                 goto out;
565         }
566         if (!(response = malloc(response_len))) {
567                 retval = CHAP_TARGET_ERROR;
568                 goto out;
569         }
570
571         if (chap_decode_string(value, challenge, challenge_len, encoding_format) < 0) {
572                 retval = CHAP_INITIATOR_ERROR;
573                 goto out;
574         }
575
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,
579                             challenge_len)) {
580                         /* ToDo: RFC 3720 demands to close TCP conn */
581                         log_warning("CHAP target auth.: "
582                                     "initiator %s reflected our challenge",
583                                     conn->initiator);
584                         retval = CHAP_INITIATOR_ERROR;
585                         goto out;
586                 }
587         }
588
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);
593                 break;
594         case CHAP_DIGEST_ALG_SHA1:
595                 chap_calc_digest_sha1(chap_id, pass, strlen(pass),
596                                       challenge, challenge_len, digest);
597                 break;
598         default:
599                 retval = CHAP_TARGET_ERROR;
600                 goto out;
601         }
602
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);
607
608         conn->state = STATE_SECURITY_DONE;
609  out:
610         if (challenge)
611                 free(challenge);
612         if (digest)
613                 free(digest);
614         if (response)
615                 free(response);
616         return retval;
617 }
618
619 int cmnd_exec_auth_chap(struct connection *conn)
620 {
621         int res;
622
623         switch(conn->auth_state) {
624         case CHAP_AUTH_STATE_START:
625                 res = chap_initiator_auth_create_challenge(conn);
626                 break;
627         case CHAP_AUTH_STATE_CHALLENGE:
628                 res = chap_initiator_auth_check_response(conn);
629                 if (res < 0)
630                         break;
631                 /* fall through */
632         case CHAP_AUTH_STATE_RESPONSE:
633                 res = chap_target_auth_create_response(conn);
634                 break;
635         default:
636                 log_error("%s(%d): BUG. unknown conn->auth_state %d",
637                           __FUNCTION__, __LINE__, conn->auth_state);
638                 res = CHAP_TARGET_ERROR;
639         }
640
641         return res;
642 }