GNU libmicrohttpd
0.9.5
|
00001 /* 00002 This file is part of libmicrohttpd 00003 (C) 2010 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 */ 00019 00027 #include "platform.h" 00028 #include "internal.h" 00029 #include "md5.h" 00030 #include "base64.h" 00031 00032 #define HASH_MD5_HEX_LEN (2 * MD5_DIGEST_SIZE) 00033 00037 #define _BASE "Digest " 00038 00042 #define _BASIC_BASE "Basic " 00043 00047 #define MAX_USERNAME_LENGTH 128 00048 00052 #define MAX_REALM_LENGTH 256 00053 00057 #define MAX_AUTH_RESPONSE_LENGTH 128 00058 00066 static void 00067 cvthex(const unsigned char *bin, 00068 size_t len, 00069 char *hex) 00070 { 00071 size_t i; 00072 unsigned int j; 00073 00074 for (i = 0; i < len; ++i) 00075 { 00076 j = (bin[i] >> 4) & 0x0f; 00077 hex[i * 2] = j <= 9 ? (j + '0') : (j + 'a' - 10); 00078 j = bin[i] & 0x0f; 00079 hex[i * 2 + 1] = j <= 9 ? (j + '0') : (j + 'a' - 10); 00080 } 00081 hex[len * 2] = '\0'; 00082 } 00083 00096 static void 00097 digest_calc_ha1(const char *alg, 00098 const char *username, 00099 const char *realm, 00100 const char *password, 00101 const char *nonce, 00102 const char *cnonce, 00103 char *sessionkey) 00104 { 00105 struct MD5Context md5; 00106 unsigned char ha1[MD5_DIGEST_SIZE]; 00107 00108 MD5Init (&md5); 00109 MD5Update (&md5, username, strlen (username)); 00110 MD5Update (&md5, ":", 1); 00111 MD5Update (&md5, realm, strlen (realm)); 00112 MD5Update (&md5, ":", 1); 00113 MD5Update (&md5, password, strlen (password)); 00114 MD5Final (ha1, &md5); 00115 if (0 == strcasecmp(alg, "md5-sess")) 00116 { 00117 MD5Init (&md5); 00118 MD5Update (&md5, ha1, sizeof (ha1)); 00119 MD5Update (&md5, ":", 1); 00120 MD5Update (&md5, nonce, strlen (nonce)); 00121 MD5Update (&md5, ":", 1); 00122 MD5Update (&md5, cnonce, strlen (cnonce)); 00123 MD5Final (ha1, &md5); 00124 } 00125 cvthex(ha1, sizeof (ha1), sessionkey); 00126 } 00127 00128 00142 static void 00143 digest_calc_response(const char *ha1, 00144 const char *nonce, 00145 const char *noncecount, 00146 const char *cnonce, 00147 const char *qop, 00148 const char *method, 00149 const char *uri, 00150 const char *hentity, 00151 char *response) 00152 { 00153 struct MD5Context md5; 00154 unsigned char ha2[MD5_DIGEST_SIZE]; 00155 unsigned char resphash[MD5_DIGEST_SIZE]; 00156 char ha2hex[HASH_MD5_HEX_LEN + 1]; 00157 00158 MD5Init (&md5); 00159 MD5Update (&md5, method, strlen(method)); 00160 MD5Update (&md5, ":", 1); 00161 MD5Update (&md5, uri, strlen(uri)); 00162 #if 0 00163 if (strcasecmp(qop, "auth-int") == 0) 00164 { 00165 /* This is dead code since the rest of this module does 00166 not support auth-int. */ 00167 MD5Update (&md5, ":", 1); 00168 if (hentity != NULL) 00169 MD5Update (&md5, hentity, strlen(hentity)); 00170 } 00171 #endif 00172 MD5Final (ha2, &md5); 00173 cvthex(ha2, MD5_DIGEST_SIZE, ha2hex); 00174 MD5Init (&md5); 00175 /* calculate response */ 00176 MD5Update (&md5, ha1, HASH_MD5_HEX_LEN); 00177 MD5Update (&md5, ":", 1); 00178 MD5Update (&md5, nonce, strlen(nonce)); 00179 MD5Update (&md5, ":", 1); 00180 if ('\0' != *qop) 00181 { 00182 MD5Update (&md5, noncecount, strlen(noncecount)); 00183 MD5Update (&md5, ":", 1); 00184 MD5Update (&md5, cnonce, strlen(cnonce)); 00185 MD5Update (&md5, ":", 1); 00186 MD5Update (&md5, qop, strlen(qop)); 00187 MD5Update (&md5, ":", 1); 00188 } 00189 MD5Update (&md5, ha2hex, HASH_MD5_HEX_LEN); 00190 MD5Final (resphash, &md5); 00191 cvthex(resphash, sizeof (resphash), response); 00192 } 00193 00194 00209 static int 00210 lookup_sub_value(char *dest, 00211 size_t size, 00212 const char *data, 00213 const char *key) 00214 { 00215 size_t keylen = strlen(key); 00216 size_t len; 00217 const char *ptr = data; 00218 const char *eq; 00219 const char *q1; 00220 const char *q2; 00221 const char *qn; 00222 00223 if (0 == size) 00224 return 0; 00225 while ('\0' != *ptr) 00226 { 00227 if (NULL == (eq = strstr (ptr, "="))) 00228 return 0; 00229 q1 = eq + 1; 00230 while (' ' == *q1) 00231 q1++; 00232 if ('\"' != *q1) 00233 { 00234 q2 = strstr (q1, ","); 00235 qn = q2; 00236 } 00237 else 00238 { 00239 q1++; 00240 q2 = strstr (q1, "\""); 00241 if (NULL == q2) 00242 return 0; /* end quote not found */ 00243 qn = q2 + 1; 00244 } 00245 if ( (0 == strncasecmp (ptr, 00246 key, 00247 keylen)) && 00248 (eq == &ptr[keylen]) ) 00249 { 00250 if (q2 == NULL) 00251 { 00252 len = strlen (q1) + 1; 00253 if (size > len) 00254 size = len; 00255 size--; 00256 strncpy (dest, 00257 q1, 00258 size); 00259 dest[size] = '\0'; 00260 return size; 00261 } 00262 else 00263 { 00264 if (size > (q2 - q1) + 1) 00265 size = (q2 - q1) + 1; 00266 size--; 00267 memcpy (dest, 00268 q1, 00269 size); 00270 dest[size] = '\0'; 00271 return size; 00272 } 00273 } 00274 if (NULL == qn) 00275 return 0; 00276 ptr = strstr (qn, ","); 00277 if (NULL == ptr) 00278 return 0; 00279 ptr++; 00280 while (' ' == *ptr) 00281 ptr++; 00282 } 00283 return 0; 00284 } 00285 00286 00296 static int 00297 check_nonce_nc (struct MHD_Connection *connection, 00298 const char *nonce, 00299 unsigned int nc) 00300 { 00301 uint32_t off; 00302 uint32_t mod; 00303 const char *np; 00304 00305 mod = connection->daemon->nonce_nc_size; 00306 if (0 == mod) 00307 return MHD_NO; /* no array! */ 00308 /* super-fast xor-based "hash" function for HT lookup in nonce array */ 00309 off = 0; 00310 np = nonce; 00311 while (*np != '\0') 00312 { 00313 off = (off << 8) | (*np ^ (off >> 24)); 00314 np++; 00315 } 00316 off = off % mod; 00317 /* 00318 * Look for the nonce, if it does exist and its corresponding 00319 * nonce counter is less than the current nonce counter by 1, 00320 * then only increase the nonce counter by one. 00321 */ 00322 00323 pthread_mutex_lock(&connection->daemon->nnc_lock); 00324 if (nc == 0) 00325 { 00326 strcpy(connection->daemon->nnc[off].nonce, 00327 nonce); 00328 connection->daemon->nnc[off].nc = 0; 00329 pthread_mutex_unlock(&connection->daemon->nnc_lock); 00330 return MHD_YES; 00331 } 00332 if ( (nc <= connection->daemon->nnc[off].nc) || 00333 (0 != strcmp(connection->daemon->nnc[off].nonce, nonce)) ) 00334 { 00335 pthread_mutex_unlock(&connection->daemon->nnc_lock); 00336 #if HAVE_MESSAGES 00337 MHD_DLOG (connection->daemon, 00338 "Stale nonce received. If this happens a lot, you should probably increase the size of the nonce array.\n"); 00339 #endif 00340 return MHD_NO; 00341 } 00342 connection->daemon->nnc[off].nc = nc; 00343 pthread_mutex_unlock(&connection->daemon->nnc_lock); 00344 return MHD_YES; 00345 } 00346 00347 00355 char * 00356 MHD_digest_auth_get_username(struct MHD_Connection *connection) 00357 { 00358 size_t len; 00359 char user[MAX_USERNAME_LENGTH]; 00360 const char *header; 00361 00362 header = MHD_lookup_connection_value(connection, 00363 MHD_HEADER_KIND, 00364 MHD_HTTP_HEADER_AUTHORIZATION); 00365 if (header == NULL) 00366 return NULL; 00367 if (strncmp(header, _BASE, strlen(_BASE)) != 0) 00368 return NULL; 00369 header += strlen (_BASE); 00370 len = lookup_sub_value(user, 00371 sizeof (user), 00372 header, 00373 "username"); 00374 if (!len) 00375 return NULL; 00376 return strdup(user); 00377 } 00378 00379 00393 static void 00394 calculate_nonce (uint32_t nonce_time, 00395 const char *method, 00396 const char *rnd, 00397 unsigned int rnd_size, 00398 const char *uri, 00399 const char *realm, 00400 char *nonce) 00401 { 00402 struct MD5Context md5; 00403 unsigned char timestamp[4]; 00404 unsigned char tmpnonce[MD5_DIGEST_SIZE]; 00405 char timestamphex[sizeof(timestamp)*2+1]; 00406 00407 MD5Init (&md5); 00408 timestamp[0] = (nonce_time & 0xff000000) >> 0x18; 00409 timestamp[1] = (nonce_time & 0x00ff0000) >> 0x10; 00410 timestamp[2] = (nonce_time & 0x0000ff00) >> 0x08; 00411 timestamp[3] = (nonce_time & 0x000000ff); 00412 MD5Update(&md5, timestamp, 4); 00413 MD5Update(&md5, ":", 1); 00414 MD5Update(&md5, method, strlen(method)); 00415 MD5Update(&md5, ":", 1); 00416 if (rnd_size > 0) 00417 MD5Update(&md5, rnd, rnd_size); 00418 MD5Update(&md5, ":", 1); 00419 MD5Update(&md5, uri, strlen(uri)); 00420 MD5Update(&md5, ":", 1); 00421 MD5Update(&md5, realm, strlen(realm)); 00422 MD5Final (tmpnonce, &md5); 00423 cvthex(tmpnonce, sizeof (tmpnonce), nonce); 00424 cvthex(timestamp, 4, timestamphex); 00425 strncat(nonce, timestamphex, 8); 00426 } 00427 00428 00441 int 00442 MHD_digest_auth_check(struct MHD_Connection *connection, 00443 const char *realm, 00444 const char *username, 00445 const char *password, 00446 unsigned int nonce_timeout) 00447 { 00448 size_t len; 00449 const char *header; 00450 char nonce[MAX_NONCE_LENGTH]; 00451 char cnonce[MAX_NONCE_LENGTH]; 00452 char qop[15]; /* auth,auth-int */ 00453 char nc[20]; 00454 char response[MAX_AUTH_RESPONSE_LENGTH]; 00455 const char *hentity = NULL; /* "auth-int" is not supported */ 00456 char ha1[HASH_MD5_HEX_LEN + 1]; 00457 char respexp[HASH_MD5_HEX_LEN + 1]; 00458 char noncehashexp[HASH_MD5_HEX_LEN + 9]; 00459 uint32_t nonce_time; 00460 uint32_t t; 00461 size_t left; /* number of characters left in 'header' for 'uri' */ 00462 unsigned int nci; 00463 00464 header = MHD_lookup_connection_value(connection, 00465 MHD_HEADER_KIND, 00466 MHD_HTTP_HEADER_AUTHORIZATION); 00467 if (header == NULL) 00468 return MHD_NO; 00469 if (strncmp(header, _BASE, strlen(_BASE)) != 0) 00470 return MHD_NO; 00471 header += strlen (_BASE); 00472 left = strlen (header); 00473 00474 { 00475 char un[MAX_USERNAME_LENGTH]; 00476 len = lookup_sub_value(un, 00477 sizeof (un), 00478 header, "username"); 00479 if ( (!len) || 00480 (strcmp(username, un) != 0) ) 00481 return MHD_NO; 00482 left -= strlen ("username") + len; 00483 } 00484 00485 { 00486 char r[MAX_REALM_LENGTH]; 00487 len = lookup_sub_value(r, 00488 sizeof (r), 00489 header, "realm"); 00490 if ( (!len) || 00491 (strcmp(realm, r) != 0) ) 00492 return MHD_NO; 00493 left -= strlen ("realm") + len; 00494 } 00495 00496 if (0 == (len = lookup_sub_value(nonce, 00497 sizeof (nonce), 00498 header, "nonce"))) 00499 return MHD_NO; 00500 left -= strlen ("nonce") + len; 00501 00502 { 00503 char uri[left]; 00504 00505 if (0 == lookup_sub_value(uri, 00506 sizeof (uri), 00507 header, "uri")) 00508 return MHD_NO; 00509 00510 /* 8 = 4 hexadecimal numbers for the timestamp */ 00511 nonce_time = strtoul(nonce + len - 8, (char **)NULL, 16); 00512 t = (uint32_t) time(NULL); 00513 /* 00514 * First level vetting for the nonce validity 00515 * if the timestamp attached to the nonce 00516 * exceeds `nonce_timeout' then the nonce is 00517 * invalid. 00518 */ 00519 if (t > nonce_time + nonce_timeout) 00520 return MHD_INVALID_NONCE; 00521 calculate_nonce (nonce_time, 00522 connection->method, 00523 connection->daemon->digest_auth_random, 00524 connection->daemon->digest_auth_rand_size, 00525 uri, 00526 realm, 00527 noncehashexp); 00528 /* 00529 * Second level vetting for the nonce validity 00530 * if the timestamp attached to the nonce is valid 00531 * and possibly fabricated (in case of an attack) 00532 * the attacker must also know the random seed to be 00533 * able to generate a "sane" nonce, which if he does 00534 * not, the nonce fabrication process going to be 00535 * very hard to achieve. 00536 */ 00537 00538 if (0 != strcmp(nonce, noncehashexp)) 00539 return MHD_INVALID_NONCE; 00540 if ( (0 == lookup_sub_value(cnonce, 00541 sizeof (cnonce), 00542 header, "cnonce")) || 00543 (0 == lookup_sub_value(qop, sizeof (qop), header, "qop")) || 00544 ( (0 != strcmp (qop, "auth")) && 00545 (0 != strcmp (qop, "")) ) || 00546 (0 == lookup_sub_value(nc, sizeof (nc), header, "nc")) || 00547 (1 != sscanf (nc, "%u", &nci)) || 00548 (0 == lookup_sub_value(response, sizeof (response), header, "response")) ) 00549 return MHD_NO; 00550 00551 /* 00552 * Checking if that combination of nonce and nc is sound 00553 * and not a replay attack attempt. Also adds the nonce 00554 * to the nonce-nc map if it does not exist there. 00555 */ 00556 00557 if (MHD_YES != check_nonce_nc (connection, nonce, nci)) 00558 return MHD_NO; 00559 00560 digest_calc_ha1("md5", 00561 username, 00562 realm, 00563 password, 00564 nonce, 00565 cnonce, 00566 ha1); 00567 digest_calc_response(ha1, 00568 nonce, 00569 nc, 00570 cnonce, 00571 qop, 00572 connection->method, 00573 uri, 00574 hentity, 00575 respexp); 00576 return strcmp(response, respexp) == 0 ? MHD_YES : MHD_NO; 00577 } 00578 } 00579 00580 00591 int 00592 MHD_queue_auth_fail_response(struct MHD_Connection *connection, 00593 const char *realm, 00594 const char *opaque, 00595 struct MHD_Response *response, 00596 int signal_stale) 00597 { 00598 int ret; 00599 size_t hlen; 00600 char nonce[HASH_MD5_HEX_LEN + 9]; 00601 00602 /* Generating the server nonce */ 00603 calculate_nonce ((uint32_t) time(NULL), 00604 connection->method, 00605 connection->daemon->digest_auth_random, 00606 connection->daemon->digest_auth_rand_size, 00607 connection->url, 00608 realm, 00609 nonce); 00610 if (MHD_YES != check_nonce_nc (connection, nonce, 0)) 00611 { 00612 #if HAVE_MESSAGES 00613 MHD_DLOG (connection->daemon, 00614 "Could not register nonce (is the nonce array size zero?).\n"); 00615 #endif 00616 return MHD_NO; 00617 } 00618 /* Building the authentication header */ 00619 hlen = snprintf(NULL, 00620 0, 00621 "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s", 00622 realm, 00623 nonce, 00624 opaque, 00625 signal_stale ? ",stale=\"true\"" : ""); 00626 { 00627 char header[hlen + 1]; 00628 snprintf(header, 00629 sizeof(header), 00630 "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s", 00631 realm, 00632 nonce, 00633 opaque, 00634 signal_stale ? ",stale=\"true\"" : ""); 00635 ret = MHD_add_response_header(response, 00636 MHD_HTTP_HEADER_WWW_AUTHENTICATE, 00637 header); 00638 } 00639 if (MHD_YES == ret) 00640 ret = MHD_queue_response(connection, 00641 MHD_HTTP_UNAUTHORIZED, 00642 response); 00643 return ret; 00644 } 00645 00646 00655 char * 00656 MHD_basic_auth_get_username_password(struct MHD_Connection *connection, 00657 char** password) 00658 { 00659 const char *header; 00660 char *decode; 00661 const char *separator; 00662 char *user; 00663 00664 header = MHD_lookup_connection_value(connection, 00665 MHD_HEADER_KIND, 00666 MHD_HTTP_HEADER_AUTHORIZATION); 00667 if (header == NULL) 00668 return NULL; 00669 if (strncmp(header, _BASIC_BASE, strlen(_BASIC_BASE)) != 0) 00670 return NULL; 00671 header += strlen(_BASIC_BASE); 00672 decode = BASE64Decode(header); 00673 if (decode == NULL) 00674 { 00675 #if HAVE_MESSAGES 00676 MHD_DLOG(connection->daemon, 00677 "Error decoding basic authentication\n"); 00678 #endif 00679 return NULL; 00680 } 00681 /* Find user:password pattern */ 00682 separator = strstr(decode, ":"); 00683 if (separator == NULL) 00684 { 00685 #if HAVE_MESSAGES 00686 MHD_DLOG(connection->daemon, 00687 "Basic authentication doesn't contain ':' separator\n"); 00688 #endif 00689 free(decode); 00690 return NULL; 00691 } 00692 user = strdup(decode); 00693 if (NULL == user) 00694 { 00695 free (decode); 00696 return NULL; 00697 } 00698 user[separator - decode] = '\0'; /* cut off at ':' */ 00699 if (password != NULL) 00700 { 00701 *password = strdup(separator + 1); 00702 if (NULL == *password) 00703 { 00704 #if HAVE_MESSAGES 00705 MHD_DLOG(connection->daemon, 00706 "Failed to allocate memory for password\n"); 00707 #endif 00708 free (decode); 00709 free (user); 00710 return NULL; 00711 } 00712 } 00713 free(decode); 00714 return user; 00715 } 00716 00717 00725 int 00726 MHD_queue_basic_auth_fail_response(struct MHD_Connection *connection, 00727 const char *realm, 00728 struct MHD_Response *response) 00729 { 00730 int ret; 00731 size_t hlen = strlen(realm) + strlen("Basic realm=\"\"") + 1; 00732 char header[hlen]; 00733 00734 snprintf(header, 00735 sizeof (header), 00736 "Basic realm=\"%s\"", 00737 realm); 00738 ret = MHD_add_response_header(response, 00739 MHD_HTTP_HEADER_WWW_AUTHENTICATE, 00740 header); 00741 if (MHD_YES == ret) 00742 ret = MHD_queue_response(connection, 00743 MHD_HTTP_UNAUTHORIZED, 00744 response); 00745 return ret; 00746 } 00747 00748 /* end of digestauth.c */