|
GNU libmicrohttpd
0.9.5
|
00001 /* 00002 This file is part of libmicrohttpd 00003 (C) 2010, 2011, 2012 Daniel Pittman and Christian Grothoff 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Lesser General Public 00007 License as published by the Free Software Foundation; either 00008 version 2.1 of the License, or (at your option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Lesser General Public License for more details. 00014 00015 You should have received a copy of the GNU Lesser General Public 00016 License along with this library; if not, write to the Free Software 00017 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00018 */ 00025 #include "platform.h" 00026 #include <limits.h> 00027 #include "internal.h" 00028 #include "md5.h" 00029 00030 #define HASH_MD5_HEX_LEN (2 * MD5_DIGEST_SIZE) 00031 00035 #define _BASE "Digest " 00036 00040 #define MAX_USERNAME_LENGTH 128 00041 00045 #define MAX_REALM_LENGTH 256 00046 00050 #define MAX_AUTH_RESPONSE_LENGTH 128 00051 00052 00060 static void 00061 cvthex (const unsigned char *bin, 00062 size_t len, 00063 char *hex) 00064 { 00065 size_t i; 00066 unsigned int j; 00067 00068 for (i = 0; i < len; ++i) 00069 { 00070 j = (bin[i] >> 4) & 0x0f; 00071 hex[i * 2] = j <= 9 ? (j + '0') : (j + 'a' - 10); 00072 j = bin[i] & 0x0f; 00073 hex[i * 2 + 1] = j <= 9 ? (j + '0') : (j + 'a' - 10); 00074 } 00075 hex[len * 2] = '\0'; 00076 } 00077 00078 00091 static void 00092 digest_calc_ha1 (const char *alg, 00093 const char *username, 00094 const char *realm, 00095 const char *password, 00096 const char *nonce, 00097 const char *cnonce, 00098 char *sessionkey) 00099 { 00100 struct MD5Context md5; 00101 unsigned char ha1[MD5_DIGEST_SIZE]; 00102 00103 MD5Init (&md5); 00104 MD5Update (&md5, username, strlen (username)); 00105 MD5Update (&md5, ":", 1); 00106 MD5Update (&md5, realm, strlen (realm)); 00107 MD5Update (&md5, ":", 1); 00108 MD5Update (&md5, password, strlen (password)); 00109 MD5Final (ha1, &md5); 00110 if (0 == strcasecmp (alg, "md5-sess")) 00111 { 00112 MD5Init (&md5); 00113 MD5Update (&md5, ha1, sizeof (ha1)); 00114 MD5Update (&md5, ":", 1); 00115 MD5Update (&md5, nonce, strlen (nonce)); 00116 MD5Update (&md5, ":", 1); 00117 MD5Update (&md5, cnonce, strlen (cnonce)); 00118 MD5Final (ha1, &md5); 00119 } 00120 cvthex (ha1, sizeof (ha1), sessionkey); 00121 } 00122 00123 00137 static void 00138 digest_calc_response (const char *ha1, 00139 const char *nonce, 00140 const char *noncecount, 00141 const char *cnonce, 00142 const char *qop, 00143 const char *method, 00144 const char *uri, 00145 const char *hentity, 00146 char *response) 00147 { 00148 struct MD5Context md5; 00149 unsigned char ha2[MD5_DIGEST_SIZE]; 00150 unsigned char resphash[MD5_DIGEST_SIZE]; 00151 char ha2hex[HASH_MD5_HEX_LEN + 1]; 00152 00153 MD5Init (&md5); 00154 MD5Update (&md5, method, strlen(method)); 00155 MD5Update (&md5, ":", 1); 00156 MD5Update (&md5, uri, strlen(uri)); 00157 #if 0 00158 if (0 == strcasecmp(qop, "auth-int")) 00159 { 00160 /* This is dead code since the rest of this module does 00161 not support auth-int. */ 00162 MD5Update (&md5, ":", 1); 00163 if (NULL != hentity) 00164 MD5Update (&md5, hentity, strlen(hentity)); 00165 } 00166 #endif 00167 MD5Final (ha2, &md5); 00168 cvthex (ha2, MD5_DIGEST_SIZE, ha2hex); 00169 MD5Init (&md5); 00170 /* calculate response */ 00171 MD5Update (&md5, ha1, HASH_MD5_HEX_LEN); 00172 MD5Update (&md5, ":", 1); 00173 MD5Update (&md5, nonce, strlen(nonce)); 00174 MD5Update (&md5, ":", 1); 00175 if ('\0' != *qop) 00176 { 00177 MD5Update (&md5, noncecount, strlen(noncecount)); 00178 MD5Update (&md5, ":", 1); 00179 MD5Update (&md5, cnonce, strlen(cnonce)); 00180 MD5Update (&md5, ":", 1); 00181 MD5Update (&md5, qop, strlen(qop)); 00182 MD5Update (&md5, ":", 1); 00183 } 00184 MD5Update (&md5, ha2hex, HASH_MD5_HEX_LEN); 00185 MD5Final (resphash, &md5); 00186 cvthex (resphash, sizeof (resphash), response); 00187 } 00188 00189 00204 static int 00205 lookup_sub_value (char *dest, 00206 size_t size, 00207 const char *data, 00208 const char *key) 00209 { 00210 size_t keylen; 00211 size_t len; 00212 const char *ptr; 00213 const char *eq; 00214 const char *q1; 00215 const char *q2; 00216 const char *qn; 00217 00218 if (0 == size) 00219 return 0; 00220 keylen = strlen (key); 00221 ptr = data; 00222 while ('\0' != *ptr) 00223 { 00224 if (NULL == (eq = strchr (ptr, '='))) 00225 return 0; 00226 q1 = eq + 1; 00227 while (' ' == *q1) 00228 q1++; 00229 if ('\"' != *q1) 00230 { 00231 q2 = strchr (q1, ','); 00232 qn = q2; 00233 } 00234 else 00235 { 00236 q1++; 00237 q2 = strchr (q1, '\"'); 00238 if (NULL == q2) 00239 return 0; /* end quote not found */ 00240 qn = q2 + 1; 00241 } 00242 if ( (0 == strncasecmp (ptr, 00243 key, 00244 keylen)) && 00245 (eq == &ptr[keylen]) ) 00246 { 00247 if (NULL == q2) 00248 { 00249 len = strlen (q1) + 1; 00250 if (size > len) 00251 size = len; 00252 size--; 00253 strncpy (dest, 00254 q1, 00255 size); 00256 dest[size] = '\0'; 00257 return size; 00258 } 00259 else 00260 { 00261 if (size > (q2 - q1) + 1) 00262 size = (q2 - q1) + 1; 00263 size--; 00264 memcpy (dest, 00265 q1, 00266 size); 00267 dest[size] = '\0'; 00268 return size; 00269 } 00270 } 00271 if (NULL == qn) 00272 return 0; 00273 ptr = strchr (qn, ','); 00274 if (NULL == ptr) 00275 return 0; 00276 ptr++; 00277 while (' ' == *ptr) 00278 ptr++; 00279 } 00280 return 0; 00281 } 00282 00283 00293 static int 00294 check_nonce_nc (struct MHD_Connection *connection, 00295 const char *nonce, 00296 unsigned long int nc) 00297 { 00298 uint32_t off; 00299 uint32_t mod; 00300 const char *np; 00301 00302 mod = connection->daemon->nonce_nc_size; 00303 if (0 == mod) 00304 return MHD_NO; /* no array! */ 00305 /* super-fast xor-based "hash" function for HT lookup in nonce array */ 00306 off = 0; 00307 np = nonce; 00308 while ('\0' != *np) 00309 { 00310 off = (off << 8) | (*np ^ (off >> 24)); 00311 np++; 00312 } 00313 off = off % mod; 00314 /* 00315 * Look for the nonce, if it does exist and its corresponding 00316 * nonce counter is less than the current nonce counter by 1, 00317 * then only increase the nonce counter by one. 00318 */ 00319 00320 pthread_mutex_lock (&connection->daemon->nnc_lock); 00321 if (0 == nc) 00322 { 00323 strcpy(connection->daemon->nnc[off].nonce, 00324 nonce); 00325 connection->daemon->nnc[off].nc = 0; 00326 pthread_mutex_unlock (&connection->daemon->nnc_lock); 00327 return MHD_YES; 00328 } 00329 if ( (nc <= connection->daemon->nnc[off].nc) || 00330 (0 != strcmp(connection->daemon->nnc[off].nonce, nonce)) ) 00331 { 00332 pthread_mutex_unlock (&connection->daemon->nnc_lock); 00333 #if HAVE_MESSAGES 00334 MHD_DLOG (connection->daemon, 00335 "Stale nonce received. If this happens a lot, you should probably increase the size of the nonce array.\n"); 00336 #endif 00337 return MHD_NO; 00338 } 00339 connection->daemon->nnc[off].nc = nc; 00340 pthread_mutex_unlock (&connection->daemon->nnc_lock); 00341 return MHD_YES; 00342 } 00343 00344 00352 char * 00353 MHD_digest_auth_get_username(struct MHD_Connection *connection) 00354 { 00355 size_t len; 00356 char user[MAX_USERNAME_LENGTH]; 00357 const char *header; 00358 00359 if (NULL == (header = MHD_lookup_connection_value (connection, 00360 MHD_HEADER_KIND, 00361 MHD_HTTP_HEADER_AUTHORIZATION))) 00362 return NULL; 00363 if (0 != strncmp (header, _BASE, strlen (_BASE))) 00364 return NULL; 00365 header += strlen (_BASE); 00366 if (0 == (len = lookup_sub_value (user, 00367 sizeof (user), 00368 header, 00369 "username"))) 00370 return NULL; 00371 return strdup (user); 00372 } 00373 00374 00388 static void 00389 calculate_nonce (uint32_t nonce_time, 00390 const char *method, 00391 const char *rnd, 00392 unsigned int rnd_size, 00393 const char *uri, 00394 const char *realm, 00395 char *nonce) 00396 { 00397 struct MD5Context md5; 00398 unsigned char timestamp[4]; 00399 unsigned char tmpnonce[MD5_DIGEST_SIZE]; 00400 char timestamphex[sizeof(timestamp) * 2 + 1]; 00401 00402 MD5Init (&md5); 00403 timestamp[0] = (nonce_time & 0xff000000) >> 0x18; 00404 timestamp[1] = (nonce_time & 0x00ff0000) >> 0x10; 00405 timestamp[2] = (nonce_time & 0x0000ff00) >> 0x08; 00406 timestamp[3] = (nonce_time & 0x000000ff); 00407 MD5Update (&md5, timestamp, 4); 00408 MD5Update (&md5, ":", 1); 00409 MD5Update (&md5, method, strlen(method)); 00410 MD5Update (&md5, ":", 1); 00411 if (rnd_size > 0) 00412 MD5Update (&md5, rnd, rnd_size); 00413 MD5Update (&md5, ":", 1); 00414 MD5Update (&md5, uri, strlen(uri)); 00415 MD5Update (&md5, ":", 1); 00416 MD5Update (&md5, realm, strlen(realm)); 00417 MD5Final (tmpnonce, &md5); 00418 cvthex (tmpnonce, sizeof (tmpnonce), nonce); 00419 cvthex (timestamp, 4, timestamphex); 00420 strncat (nonce, timestamphex, 8); 00421 } 00422 00423 00434 static int 00435 test_header (struct MHD_Connection *connection, 00436 const char *key, 00437 const char *value) 00438 { 00439 struct MHD_HTTP_Header *pos; 00440 00441 for (pos = connection->headers_received; NULL != pos; pos = pos->next) 00442 { 00443 if (MHD_GET_ARGUMENT_KIND != pos->kind) 00444 continue; 00445 if (0 != strcmp (key, pos->header)) 00446 continue; 00447 if ( (NULL == value) && 00448 (NULL == pos->value) ) 00449 return MHD_YES; 00450 if ( (NULL == value) || 00451 (NULL == pos->value) || 00452 (0 != strcmp (value, pos->value)) ) 00453 continue; 00454 return MHD_YES; 00455 } 00456 return MHD_NO; 00457 } 00458 00459 00470 static int 00471 check_argument_match (struct MHD_Connection *connection, 00472 const char *args) 00473 { 00474 struct MHD_HTTP_Header *pos; 00475 size_t slen = strlen (args) + 1; 00476 char argb[slen]; 00477 char *argp; 00478 char *equals; 00479 char *amper; 00480 unsigned int num_headers; 00481 00482 num_headers = 0; 00483 memcpy (argb, args, slen); 00484 argp = argb; 00485 while ( (NULL != argp) && 00486 ('\0' != argp[0]) ) 00487 { 00488 equals = strchr (argp, '='); 00489 if (NULL == equals) 00490 { 00491 /* add with 'value' NULL */ 00492 connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls, 00493 connection, 00494 argp); 00495 if (MHD_YES != test_header (connection, argp, NULL)) 00496 return MHD_NO; 00497 num_headers++; 00498 break; 00499 } 00500 equals[0] = '\0'; 00501 equals++; 00502 amper = strchr (equals, '&'); 00503 if (NULL != amper) 00504 { 00505 amper[0] = '\0'; 00506 amper++; 00507 } 00508 connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls, 00509 connection, 00510 argp); 00511 connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls, 00512 connection, 00513 equals); 00514 if (! test_header (connection, argp, equals)) 00515 return MHD_NO; 00516 num_headers++; 00517 argp = amper; 00518 } 00519 00520 /* also check that the number of headers matches */ 00521 for (pos = connection->headers_received; NULL != pos; pos = pos->next) 00522 { 00523 if (MHD_GET_ARGUMENT_KIND != pos->kind) 00524 continue; 00525 num_headers--; 00526 } 00527 if (0 != num_headers) 00528 return MHD_NO; 00529 return MHD_YES; 00530 } 00531 00532 00545 int 00546 MHD_digest_auth_check (struct MHD_Connection *connection, 00547 const char *realm, 00548 const char *username, 00549 const char *password, 00550 unsigned int nonce_timeout) 00551 { 00552 size_t len; 00553 const char *header; 00554 char *end; 00555 char nonce[MAX_NONCE_LENGTH]; 00556 char cnonce[MAX_NONCE_LENGTH]; 00557 char qop[15]; /* auth,auth-int */ 00558 char nc[20]; 00559 char response[MAX_AUTH_RESPONSE_LENGTH]; 00560 const char *hentity = NULL; /* "auth-int" is not supported */ 00561 char ha1[HASH_MD5_HEX_LEN + 1]; 00562 char respexp[HASH_MD5_HEX_LEN + 1]; 00563 char noncehashexp[HASH_MD5_HEX_LEN + 9]; 00564 uint32_t nonce_time; 00565 uint32_t t; 00566 size_t left; /* number of characters left in 'header' for 'uri' */ 00567 unsigned long int nci; 00568 00569 header = MHD_lookup_connection_value (connection, 00570 MHD_HEADER_KIND, 00571 MHD_HTTP_HEADER_AUTHORIZATION); 00572 if (NULL == header) 00573 return MHD_NO; 00574 if (0 != strncmp(header, _BASE, strlen(_BASE))) 00575 return MHD_NO; 00576 header += strlen (_BASE); 00577 left = strlen (header); 00578 00579 { 00580 char un[MAX_USERNAME_LENGTH]; 00581 00582 len = lookup_sub_value (un, 00583 sizeof (un), 00584 header, "username"); 00585 if ( (0 == len) || 00586 (0 != strcmp(username, un)) ) 00587 return MHD_NO; 00588 left -= strlen ("username") + len; 00589 } 00590 00591 { 00592 char r[MAX_REALM_LENGTH]; 00593 00594 len = lookup_sub_value(r, 00595 sizeof (r), 00596 header, "realm"); 00597 if ( (0 == len) || 00598 (0 != strcmp(realm, r)) ) 00599 return MHD_NO; 00600 left -= strlen ("realm") + len; 00601 } 00602 00603 if (0 == (len = lookup_sub_value (nonce, 00604 sizeof (nonce), 00605 header, "nonce"))) 00606 return MHD_NO; 00607 left -= strlen ("nonce") + len; 00608 00609 { 00610 char uri[left]; 00611 00612 if (0 == lookup_sub_value(uri, 00613 sizeof (uri), 00614 header, "uri")) 00615 return MHD_NO; 00616 00617 /* 8 = 4 hexadecimal numbers for the timestamp */ 00618 nonce_time = strtoul(nonce + len - 8, (char **)NULL, 16); 00619 t = (uint32_t) MHD_monotonic_time(); 00620 /* 00621 * First level vetting for the nonce validity if the timestamp 00622 * attached to the nonce exceeds `nonce_timeout' then the nonce is 00623 * invalid. 00624 */ 00625 if ( (t > nonce_time + nonce_timeout) || 00626 (nonce_time + nonce_timeout < nonce_time) ) 00627 return MHD_INVALID_NONCE; 00628 if (0 != strncmp (uri, 00629 connection->url, 00630 strlen (connection->url))) 00631 { 00632 #if HAVE_MESSAGES 00633 MHD_DLOG (connection->daemon, 00634 "Authentication failed, URI does not match.\n"); 00635 #endif 00636 return MHD_NO; 00637 } 00638 { 00639 const char *args = strchr (uri, '?'); 00640 00641 if (NULL == args) 00642 args = ""; 00643 else 00644 args++; 00645 if (MHD_YES != 00646 check_argument_match (connection, 00647 args) ) 00648 { 00649 #if HAVE_MESSAGES 00650 MHD_DLOG (connection->daemon, 00651 "Authentication failed, arguments do not match.\n"); 00652 #endif 00653 return MHD_NO; 00654 } 00655 } 00656 calculate_nonce (nonce_time, 00657 connection->method, 00658 connection->daemon->digest_auth_random, 00659 connection->daemon->digest_auth_rand_size, 00660 connection->url, 00661 realm, 00662 noncehashexp); 00663 /* 00664 * Second level vetting for the nonce validity 00665 * if the timestamp attached to the nonce is valid 00666 * and possibly fabricated (in case of an attack) 00667 * the attacker must also know the random seed to be 00668 * able to generate a "sane" nonce, which if he does 00669 * not, the nonce fabrication process going to be 00670 * very hard to achieve. 00671 */ 00672 00673 if (0 != strcmp (nonce, noncehashexp)) 00674 return MHD_INVALID_NONCE; 00675 if ( (0 == lookup_sub_value (cnonce, 00676 sizeof (cnonce), 00677 header, "cnonce")) || 00678 (0 == lookup_sub_value (qop, sizeof (qop), header, "qop")) || 00679 ( (0 != strcmp (qop, "auth")) && 00680 (0 != strcmp (qop, "")) ) || 00681 (0 == lookup_sub_value (nc, sizeof (nc), header, "nc")) || 00682 (0 == lookup_sub_value (response, sizeof (response), header, "response")) ) 00683 { 00684 #if HAVE_MESSAGES 00685 MHD_DLOG (connection->daemon, 00686 "Authentication failed, invalid format.\n"); 00687 #endif 00688 return MHD_NO; 00689 } 00690 nci = strtoul (nc, &end, 16); 00691 if ( ('\0' != *end) || 00692 ( (LONG_MAX == nci) && 00693 (ERANGE == errno) ) ) 00694 { 00695 #if HAVE_MESSAGES 00696 MHD_DLOG (connection->daemon, 00697 "Authentication failed, invalid format.\n"); 00698 #endif 00699 return MHD_NO; /* invalid nonce format */ 00700 } 00701 /* 00702 * Checking if that combination of nonce and nc is sound 00703 * and not a replay attack attempt. Also adds the nonce 00704 * to the nonce-nc map if it does not exist there. 00705 */ 00706 00707 if (MHD_YES != check_nonce_nc (connection, nonce, nci)) 00708 return MHD_NO; 00709 00710 digest_calc_ha1("md5", 00711 username, 00712 realm, 00713 password, 00714 nonce, 00715 cnonce, 00716 ha1); 00717 digest_calc_response (ha1, 00718 nonce, 00719 nc, 00720 cnonce, 00721 qop, 00722 connection->method, 00723 uri, 00724 hentity, 00725 respexp); 00726 return (0 == strcmp(response, respexp)) 00727 ? MHD_YES 00728 : MHD_NO; 00729 } 00730 } 00731 00732 00743 int 00744 MHD_queue_auth_fail_response (struct MHD_Connection *connection, 00745 const char *realm, 00746 const char *opaque, 00747 struct MHD_Response *response, 00748 int signal_stale) 00749 { 00750 int ret; 00751 size_t hlen; 00752 char nonce[HASH_MD5_HEX_LEN + 9]; 00753 00754 /* Generating the server nonce */ 00755 calculate_nonce ((uint32_t) MHD_monotonic_time(), 00756 connection->method, 00757 connection->daemon->digest_auth_random, 00758 connection->daemon->digest_auth_rand_size, 00759 connection->url, 00760 realm, 00761 nonce); 00762 if (MHD_YES != check_nonce_nc (connection, nonce, 0)) 00763 { 00764 #if HAVE_MESSAGES 00765 MHD_DLOG (connection->daemon, 00766 "Could not register nonce (is the nonce array size zero?).\n"); 00767 #endif 00768 return MHD_NO; 00769 } 00770 /* Building the authentication header */ 00771 hlen = snprintf (NULL, 00772 0, 00773 "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s", 00774 realm, 00775 nonce, 00776 opaque, 00777 signal_stale 00778 ? ",stale=\"true\"" 00779 : ""); 00780 { 00781 char header[hlen + 1]; 00782 00783 snprintf (header, 00784 sizeof(header), 00785 "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s", 00786 realm, 00787 nonce, 00788 opaque, 00789 signal_stale 00790 ? ",stale=\"true\"" 00791 : ""); 00792 ret = MHD_add_response_header(response, 00793 MHD_HTTP_HEADER_WWW_AUTHENTICATE, 00794 header); 00795 } 00796 if (MHD_YES == ret) 00797 ret = MHD_queue_response(connection, 00798 MHD_HTTP_UNAUTHORIZED, 00799 response); 00800 return ret; 00801 } 00802 00803 00804 /* end of digestauth.c */