1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel (at) haxx.se>, et al. 9 * 10 * This software is licensed as described in the file COPYING, which 11 * you should have received as part of this distribution. The terms 12 * are also available at https://curl.haxx.se/docs/copyright.html. 13 * 14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell 15 * copies of the Software, and permit persons to whom the Software is 16 * furnished to do so, under the terms of the COPYING file. 17 * 18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 * KIND, either express or implied. 20 * 21 * RFC2831 DIGEST-MD5 authentication 22 * 23 ***************************************************************************/ 24 25 #include "curl_setup.h" 26 27 #if !defined(CURL_DISABLE_CRYPTO_AUTH) 28 29 #include <curl/curl.h> 30 31 #include "vauth/vauth.h" 32 #include "vauth/digest.h" 33 #include "urldata.h" 34 #include "curl_base64.h" 35 #include "curl_hmac.h" 36 #include "curl_md5.h" 37 #include "vtls/vtls.h" 38 #include "warnless.h" 39 #include "strtok.h" 40 #include "rawstr.h" 41 #include "non-ascii.h" /* included for Curl_convert_... prototypes */ 42 #include "curl_printf.h" 43 44 /* The last #include files should be: */ 45 #include "curl_memory.h" 46 #include "memdebug.h" 47 48 #if !defined(USE_WINDOWS_SSPI) 49 #define DIGEST_QOP_VALUE_AUTH (1 << 0) 50 #define DIGEST_QOP_VALUE_AUTH_INT (1 << 1) 51 #define DIGEST_QOP_VALUE_AUTH_CONF (1 << 2) 52 53 #define DIGEST_QOP_VALUE_STRING_AUTH "auth" 54 #define DIGEST_QOP_VALUE_STRING_AUTH_INT "auth-int" 55 #define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf" 56 57 /* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines. 58 It converts digest text to ASCII so the MD5 will be correct for 59 what ultimately goes over the network. 60 */ 61 #define CURL_OUTPUT_DIGEST_CONV(a, b) \ 62 result = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \ 63 if(result) { \ 64 free(b); \ 65 return result; \ 66 } 67 #endif /* !USE_WINDOWS_SSPI */ 68 69 bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, 70 const char **endptr) 71 { 72 int c; 73 bool starts_with_quote = FALSE; 74 bool escape = FALSE; 75 76 for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--);) 77 *value++ = *str++; 78 *value = 0; 79 80 if('=' != *str++) 81 /* eek, no match */ 82 return FALSE; 83 84 if('\"' == *str) { 85 /* This starts with a quote so it must end with one as well! */ 86 str++; 87 starts_with_quote = TRUE; 88 } 89 90 for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) { 91 switch(*str) { 92 case '\\': 93 if(!escape) { 94 /* possibly the start of an escaped quote */ 95 escape = TRUE; 96 *content++ = '\\'; /* Even though this is an escape character, we still 97 store it as-is in the target buffer */ 98 continue; 99 } 100 break; 101 102 case ',': 103 if(!starts_with_quote) { 104 /* This signals the end of the content if we didn't get a starting 105 quote and then we do "sloppy" parsing */ 106 c = 0; /* the end */ 107 continue; 108 } 109 break; 110 111 case '\r': 112 case '\n': 113 /* end of string */ 114 c = 0; 115 continue; 116 117 case '\"': 118 if(!escape && starts_with_quote) { 119 /* end of string */ 120 c = 0; 121 continue; 122 } 123 break; 124 } 125 126 escape = FALSE; 127 *content++ = *str; 128 } 129 130 *content = 0; 131 *endptr = str; 132 133 return TRUE; 134 } 135 136 #if !defined(USE_WINDOWS_SSPI) 137 /* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/ 138 static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */ 139 unsigned char *dest) /* 33 bytes */ 140 { 141 int i; 142 for(i = 0; i < 16; i++) 143 snprintf((char *) &dest[i * 2], 3, "%02x", source[i]); 144 } 145 146 /* Perform quoted-string escaping as described in RFC2616 and its errata */ 147 static char *auth_digest_string_quoted(const char *source) 148 { 149 char *dest, *d; 150 const char *s = source; 151 size_t n = 1; /* null terminator */ 152 153 /* Calculate size needed */ 154 while(*s) { 155 ++n; 156 if(*s == '"' || *s == '\\') { 157 ++n; 158 } 159 ++s; 160 } 161 162 dest = malloc(n); 163 if(dest) { 164 s = source; 165 d = dest; 166 while(*s) { 167 if(*s == '"' || *s == '\\') { 168 *d++ = '\\'; 169 } 170 *d++ = *s++; 171 } 172 *d = 0; 173 } 174 175 return dest; 176 } 177 178 /* Retrieves the value for a corresponding key from the challenge string 179 * returns TRUE if the key could be found, FALSE if it does not exists 180 */ 181 static bool auth_digest_get_key_value(const char *chlg, 182 const char *key, 183 char *value, 184 size_t max_val_len, 185 char end_char) 186 { 187 char *find_pos; 188 size_t i; 189 190 find_pos = strstr(chlg, key); 191 if(!find_pos) 192 return FALSE; 193 194 find_pos += strlen(key); 195 196 for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i) 197 value[i] = *find_pos++; 198 value[i] = '\0'; 199 200 return TRUE; 201 } 202 203 static CURLcode auth_digest_get_qop_values(const char *options, int *value) 204 { 205 char *tmp; 206 char *token; 207 char *tok_buf; 208 209 /* Initialise the output */ 210 *value = 0; 211 212 /* Tokenise the list of qop values. Use a temporary clone of the buffer since 213 strtok_r() ruins it. */ 214 tmp = strdup(options); 215 if(!tmp) 216 return CURLE_OUT_OF_MEMORY; 217 218 token = strtok_r(tmp, ",", &tok_buf); 219 while(token != NULL) { 220 if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH)) 221 *value |= DIGEST_QOP_VALUE_AUTH; 222 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) 223 *value |= DIGEST_QOP_VALUE_AUTH_INT; 224 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF)) 225 *value |= DIGEST_QOP_VALUE_AUTH_CONF; 226 227 token = strtok_r(NULL, ",", &tok_buf); 228 } 229 230 free(tmp); 231 232 return CURLE_OK; 233 } 234 235 /* 236 * auth_decode_digest_md5_message() 237 * 238 * This is used internally to decode an already encoded DIGEST-MD5 challenge 239 * message into the seperate attributes. 240 * 241 * Parameters: 242 * 243 * chlg64 [in] - The base64 encoded challenge message. 244 * nonce [in/out] - The buffer where the nonce will be stored. 245 * nlen [in] - The length of the nonce buffer. 246 * realm [in/out] - The buffer where the realm will be stored. 247 * rlen [in] - The length of the realm buffer. 248 * alg [in/out] - The buffer where the algorithm will be stored. 249 * alen [in] - The length of the algorithm buffer. 250 * qop [in/out] - The buffer where the qop-options will be stored. 251 * qlen [in] - The length of the qop buffer. 252 * 253 * Returns CURLE_OK on success. 254 */ 255 static CURLcode auth_decode_digest_md5_message(const char *chlg64, 256 char *nonce, size_t nlen, 257 char *realm, size_t rlen, 258 char *alg, size_t alen, 259 char *qop, size_t qlen) 260 { 261 CURLcode result = CURLE_OK; 262 unsigned char *chlg = NULL; 263 size_t chlglen = 0; 264 size_t chlg64len = strlen(chlg64); 265 266 /* Decode the base-64 encoded challenge message */ 267 if(chlg64len && *chlg64 != '=') { 268 result = Curl_base64_decode(chlg64, &chlg, &chlglen); 269 if(result) 270 return result; 271 } 272 273 /* Ensure we have a valid challenge message */ 274 if(!chlg) 275 return CURLE_BAD_CONTENT_ENCODING; 276 277 /* Retrieve nonce string from the challenge */ 278 if(!auth_digest_get_key_value((char *) chlg, "nonce=\"", nonce, nlen, 279 '\"')) { 280 free(chlg); 281 return CURLE_BAD_CONTENT_ENCODING; 282 } 283 284 /* Retrieve realm string from the challenge */ 285 if(!auth_digest_get_key_value((char *) chlg, "realm=\"", realm, rlen, 286 '\"')) { 287 /* Challenge does not have a realm, set empty string [RFC2831] page 6 */ 288 strcpy(realm, ""); 289 } 290 291 /* Retrieve algorithm string from the challenge */ 292 if(!auth_digest_get_key_value((char *) chlg, "algorithm=", alg, alen, ',')) { 293 free(chlg); 294 return CURLE_BAD_CONTENT_ENCODING; 295 } 296 297 /* Retrieve qop-options string from the challenge */ 298 if(!auth_digest_get_key_value((char *) chlg, "qop=\"", qop, qlen, '\"')) { 299 free(chlg); 300 return CURLE_BAD_CONTENT_ENCODING; 301 } 302 303 free(chlg); 304 305 return CURLE_OK; 306 } 307 308 /* 309 * Curl_auth_create_digest_md5_message() 310 * 311 * This is used to generate an already encoded DIGEST-MD5 response message 312 * ready for sending to the recipient. 313 * 314 * Parameters: 315 * 316 * data [in] - The session handle. 317 * chlg64 [in] - The base64 encoded challenge message. 318 * userp [in] - The user name. 319 * passdwp [in] - The user's password. 320 * service [in] - The service type such as http, smtp, pop or imap. 321 * outptr [in/out] - The address where a pointer to newly allocated memory 322 * holding the result will be stored upon completion. 323 * outlen [out] - The length of the output message. 324 * 325 * Returns CURLE_OK on success. 326 */ 327 CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, 328 const char *chlg64, 329 const char *userp, 330 const char *passwdp, 331 const char *service, 332 char **outptr, size_t *outlen) 333 { 334 CURLcode result = CURLE_OK; 335 size_t i; 336 MD5_context *ctxt; 337 char *response = NULL; 338 unsigned char digest[MD5_DIGEST_LEN]; 339 char HA1_hex[2 * MD5_DIGEST_LEN + 1]; 340 char HA2_hex[2 * MD5_DIGEST_LEN + 1]; 341 char resp_hash_hex[2 * MD5_DIGEST_LEN + 1]; 342 char nonce[64]; 343 char realm[128]; 344 char algorithm[64]; 345 char qop_options[64]; 346 int qop_values; 347 char cnonce[33]; 348 unsigned int entropy[4]; 349 char nonceCount[] = "00000001"; 350 char method[] = "AUTHENTICATE"; 351 char qop[] = DIGEST_QOP_VALUE_STRING_AUTH; 352 char *spn = NULL; 353 354 /* Decode the challange message */ 355 result = auth_decode_digest_md5_message(chlg64, nonce, sizeof(nonce), 356 realm, sizeof(realm), 357 algorithm, sizeof(algorithm), 358 qop_options, sizeof(qop_options)); 359 if(result) 360 return result; 361 362 /* We only support md5 sessions */ 363 if(strcmp(algorithm, "md5-sess") != 0) 364 return CURLE_BAD_CONTENT_ENCODING; 365 366 /* Get the qop-values from the qop-options */ 367 result = auth_digest_get_qop_values(qop_options, &qop_values); 368 if(result) 369 return result; 370 371 /* We only support auth quality-of-protection */ 372 if(!(qop_values & DIGEST_QOP_VALUE_AUTH)) 373 return CURLE_BAD_CONTENT_ENCODING; 374 375 /* Generate 16 bytes of random data */ 376 entropy[0] = Curl_rand(data); 377 entropy[1] = Curl_rand(data); 378 entropy[2] = Curl_rand(data); 379 entropy[3] = Curl_rand(data); 380 381 /* Convert the random data into a 32 byte hex string */ 382 snprintf(cnonce, sizeof(cnonce), "%08x%08x%08x%08x", 383 entropy[0], entropy[1], entropy[2], entropy[3]); 384 385 /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */ 386 ctxt = Curl_MD5_init(Curl_DIGEST_MD5); 387 if(!ctxt) 388 return CURLE_OUT_OF_MEMORY; 389 390 Curl_MD5_update(ctxt, (const unsigned char *) userp, 391 curlx_uztoui(strlen(userp))); 392 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 393 Curl_MD5_update(ctxt, (const unsigned char *) realm, 394 curlx_uztoui(strlen(realm))); 395 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 396 Curl_MD5_update(ctxt, (const unsigned char *) passwdp, 397 curlx_uztoui(strlen(passwdp))); 398 Curl_MD5_final(ctxt, digest); 399 400 ctxt = Curl_MD5_init(Curl_DIGEST_MD5); 401 if(!ctxt) 402 return CURLE_OUT_OF_MEMORY; 403 404 Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN); 405 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 406 Curl_MD5_update(ctxt, (const unsigned char *) nonce, 407 curlx_uztoui(strlen(nonce))); 408 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 409 Curl_MD5_update(ctxt, (const unsigned char *) cnonce, 410 curlx_uztoui(strlen(cnonce))); 411 Curl_MD5_final(ctxt, digest); 412 413 /* Convert calculated 16 octet hex into 32 bytes string */ 414 for(i = 0; i < MD5_DIGEST_LEN; i++) 415 snprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]); 416 417 /* Generate our SPN */ 418 spn = Curl_auth_build_spn(service, realm, NULL); 419 if(!spn) 420 return CURLE_OUT_OF_MEMORY; 421 422 /* Calculate H(A2) */ 423 ctxt = Curl_MD5_init(Curl_DIGEST_MD5); 424 if(!ctxt) { 425 free(spn); 426 427 return CURLE_OUT_OF_MEMORY; 428 } 429 430 Curl_MD5_update(ctxt, (const unsigned char *) method, 431 curlx_uztoui(strlen(method))); 432 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 433 Curl_MD5_update(ctxt, (const unsigned char *) spn, 434 curlx_uztoui(strlen(spn))); 435 Curl_MD5_final(ctxt, digest); 436 437 for(i = 0; i < MD5_DIGEST_LEN; i++) 438 snprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]); 439 440 /* Now calculate the response hash */ 441 ctxt = Curl_MD5_init(Curl_DIGEST_MD5); 442 if(!ctxt) { 443 free(spn); 444 445 return CURLE_OUT_OF_MEMORY; 446 } 447 448 Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN); 449 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 450 Curl_MD5_update(ctxt, (const unsigned char *) nonce, 451 curlx_uztoui(strlen(nonce))); 452 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 453 454 Curl_MD5_update(ctxt, (const unsigned char *) nonceCount, 455 curlx_uztoui(strlen(nonceCount))); 456 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 457 Curl_MD5_update(ctxt, (const unsigned char *) cnonce, 458 curlx_uztoui(strlen(cnonce))); 459 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 460 Curl_MD5_update(ctxt, (const unsigned char *) qop, 461 curlx_uztoui(strlen(qop))); 462 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 463 464 Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN); 465 Curl_MD5_final(ctxt, digest); 466 467 for(i = 0; i < MD5_DIGEST_LEN; i++) 468 snprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]); 469 470 /* Generate the response */ 471 response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\"," 472 "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s," 473 "qop=%s", 474 userp, realm, nonce, 475 cnonce, nonceCount, spn, resp_hash_hex, qop); 476 free(spn); 477 if(!response) 478 return CURLE_OUT_OF_MEMORY; 479 480 /* Base64 encode the response */ 481 result = Curl_base64_encode(data, response, 0, outptr, outlen); 482 483 free(response); 484 485 return result; 486 } 487 488 /* 489 * Curl_auth_decode_digest_http_message() 490 * 491 * This is used to decode a HTTP DIGEST challenge message into the seperate 492 * attributes. 493 * 494 * Parameters: 495 * 496 * chlg [in] - The challenge message. 497 * digest [in/out] - The digest data struct being used and modified. 498 * 499 * Returns CURLE_OK on success. 500 */ 501 CURLcode Curl_auth_decode_digest_http_message(const char *chlg, 502 struct digestdata *digest) 503 { 504 bool before = FALSE; /* got a nonce before */ 505 bool foundAuth = FALSE; 506 bool foundAuthInt = FALSE; 507 char *token = NULL; 508 char *tmp = NULL; 509 510 /* If we already have received a nonce, keep that in mind */ 511 if(digest->nonce) 512 before = TRUE; 513 514 /* Clean up any former leftovers and initialise to defaults */ 515 Curl_auth_digest_cleanup(digest); 516 517 for(;;) { 518 char value[DIGEST_MAX_VALUE_LENGTH]; 519 char content[DIGEST_MAX_CONTENT_LENGTH]; 520 521 /* Pass all additional spaces here */ 522 while(*chlg && ISSPACE(*chlg)) 523 chlg++; 524 525 /* Extract a value=content pair */ 526 if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) { 527 if(Curl_raw_equal(value, "nonce")) { 528 free(digest->nonce); 529 digest->nonce = strdup(content); 530 if(!digest->nonce) 531 return CURLE_OUT_OF_MEMORY; 532 } 533 else if(Curl_raw_equal(value, "stale")) { 534 if(Curl_raw_equal(content, "true")) { 535 digest->stale = TRUE; 536 digest->nc = 1; /* we make a new nonce now */ 537 } 538 } 539 else if(Curl_raw_equal(value, "realm")) { 540 free(digest->realm); 541 digest->realm = strdup(content); 542 if(!digest->realm) 543 return CURLE_OUT_OF_MEMORY; 544 } 545 else if(Curl_raw_equal(value, "opaque")) { 546 free(digest->opaque); 547 digest->opaque = strdup(content); 548 if(!digest->opaque) 549 return CURLE_OUT_OF_MEMORY; 550 } 551 else if(Curl_raw_equal(value, "qop")) { 552 char *tok_buf; 553 /* Tokenize the list and choose auth if possible, use a temporary 554 clone of the buffer since strtok_r() ruins it */ 555 tmp = strdup(content); 556 if(!tmp) 557 return CURLE_OUT_OF_MEMORY; 558 559 token = strtok_r(tmp, ",", &tok_buf); 560 while(token != NULL) { 561 if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH)) { 562 foundAuth = TRUE; 563 } 564 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) { 565 foundAuthInt = TRUE; 566 } 567 token = strtok_r(NULL, ",", &tok_buf); 568 } 569 570 free(tmp); 571 572 /* Select only auth or auth-int. Otherwise, ignore */ 573 if(foundAuth) { 574 free(digest->qop); 575 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH); 576 if(!digest->qop) 577 return CURLE_OUT_OF_MEMORY; 578 } 579 else if(foundAuthInt) { 580 free(digest->qop); 581 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT); 582 if(!digest->qop) 583 return CURLE_OUT_OF_MEMORY; 584 } 585 } 586 else if(Curl_raw_equal(value, "algorithm")) { 587 free(digest->algorithm); 588 digest->algorithm = strdup(content); 589 if(!digest->algorithm) 590 return CURLE_OUT_OF_MEMORY; 591 592 if(Curl_raw_equal(content, "MD5-sess")) 593 digest->algo = CURLDIGESTALGO_MD5SESS; 594 else if(Curl_raw_equal(content, "MD5")) 595 digest->algo = CURLDIGESTALGO_MD5; 596 else 597 return CURLE_BAD_CONTENT_ENCODING; 598 } 599 else { 600 /* Unknown specifier, ignore it! */ 601 } 602 } 603 else 604 break; /* We're done here */ 605 606 /* Pass all additional spaces here */ 607 while(*chlg && ISSPACE(*chlg)) 608 chlg++; 609 610 /* Allow the list to be comma-separated */ 611 if(',' == *chlg) 612 chlg++; 613 } 614 615 /* We had a nonce since before, and we got another one now without 616 'stale=true'. This means we provided bad credentials in the previous 617 request */ 618 if(before && !digest->stale) 619 return CURLE_BAD_CONTENT_ENCODING; 620 621 /* We got this header without a nonce, that's a bad Digest line! */ 622 if(!digest->nonce) 623 return CURLE_BAD_CONTENT_ENCODING; 624 625 return CURLE_OK; 626 } 627 628 /* 629 * Curl_auth_create_digest_http_message() 630 * 631 * This is used to generate a HTTP DIGEST response message ready for sending 632 * to the recipient. 633 * 634 * Parameters: 635 * 636 * data [in] - The session handle. 637 * userp [in] - The user name. 638 * passdwp [in] - The user's password. 639 * request [in] - The HTTP request. 640 * uripath [in] - The path of the HTTP uri. 641 * digest [in/out] - The digest data struct being used and modified. 642 * outptr [in/out] - The address where a pointer to newly allocated memory 643 * holding the result will be stored upon completion. 644 * outlen [out] - The length of the output message. 645 * 646 * Returns CURLE_OK on success. 647 */ 648 CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, 649 const char *userp, 650 const char *passwdp, 651 const unsigned char *request, 652 const unsigned char *uripath, 653 struct digestdata *digest, 654 char **outptr, size_t *outlen) 655 { 656 CURLcode result; 657 unsigned char md5buf[16]; /* 16 bytes/128 bits */ 658 unsigned char request_digest[33]; 659 unsigned char *md5this; 660 unsigned char ha1[33]; /* 32 digits and 1 zero byte */ 661 unsigned char ha2[33]; /* 32 digits and 1 zero byte */ 662 char cnoncebuf[33]; 663 char *cnonce = NULL; 664 size_t cnonce_sz = 0; 665 char *userp_quoted; 666 char *response = NULL; 667 char *tmp = NULL; 668 669 if(!digest->nc) 670 digest->nc = 1; 671 672 if(!digest->cnonce) { 673 snprintf(cnoncebuf, sizeof(cnoncebuf), "%08x%08x%08x%08x", 674 Curl_rand(data), Curl_rand(data), 675 Curl_rand(data), Curl_rand(data)); 676 677 result = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf), 678 &cnonce, &cnonce_sz); 679 if(result) 680 return result; 681 682 digest->cnonce = cnonce; 683 } 684 685 /* 686 If the algorithm is "MD5" or unspecified (which then defaults to MD5): 687 688 A1 = unq(username-value) ":" unq(realm-value) ":" passwd 689 690 If the algorithm is "MD5-sess" then: 691 692 A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":" 693 unq(nonce-value) ":" unq(cnonce-value) 694 */ 695 696 md5this = (unsigned char *) 697 aprintf("%s:%s:%s", userp, digest->realm, passwdp); 698 if(!md5this) 699 return CURLE_OUT_OF_MEMORY; 700 701 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ 702 Curl_md5it(md5buf, md5this); 703 free(md5this); 704 auth_digest_md5_to_ascii(md5buf, ha1); 705 706 if(digest->algo == CURLDIGESTALGO_MD5SESS) { 707 /* nonce and cnonce are OUTSIDE the hash */ 708 tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce); 709 if(!tmp) 710 return CURLE_OUT_OF_MEMORY; 711 712 CURL_OUTPUT_DIGEST_CONV(data, tmp); /* Convert on non-ASCII machines */ 713 Curl_md5it(md5buf, (unsigned char *) tmp); 714 free(tmp); 715 auth_digest_md5_to_ascii(md5buf, ha1); 716 } 717 718 /* 719 If the "qop" directive's value is "auth" or is unspecified, then A2 is: 720 721 A2 = Method ":" digest-uri-value 722 723 If the "qop" value is "auth-int", then A2 is: 724 725 A2 = Method ":" digest-uri-value ":" H(entity-body) 726 727 (The "Method" value is the HTTP request method as specified in section 728 5.1.1 of RFC 2616) 729 */ 730 731 md5this = (unsigned char *) aprintf("%s:%s", request, uripath); 732 733 if(digest->qop && Curl_raw_equal(digest->qop, "auth-int")) { 734 /* We don't support auth-int for PUT or POST at the moment. 735 TODO: replace md5 of empty string with entity-body for PUT/POST */ 736 unsigned char *md5this2 = (unsigned char *) 737 aprintf("%s:%s", md5this, "d41d8cd98f00b204e9800998ecf8427e"); 738 free(md5this); 739 md5this = md5this2; 740 } 741 742 if(!md5this) 743 return CURLE_OUT_OF_MEMORY; 744 745 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ 746 Curl_md5it(md5buf, md5this); 747 free(md5this); 748 auth_digest_md5_to_ascii(md5buf, ha2); 749 750 if(digest->qop) { 751 md5this = (unsigned char *) aprintf("%s:%s:%08x:%s:%s:%s", 752 ha1, 753 digest->nonce, 754 digest->nc, 755 digest->cnonce, 756 digest->qop, 757 ha2); 758 } 759 else { 760 md5this = (unsigned char *) aprintf("%s:%s:%s", 761 ha1, 762 digest->nonce, 763 ha2); 764 } 765 766 if(!md5this) 767 return CURLE_OUT_OF_MEMORY; 768 769 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ 770 Curl_md5it(md5buf, md5this); 771 free(md5this); 772 auth_digest_md5_to_ascii(md5buf, request_digest); 773 774 /* For test case 64 (snooped from a Mozilla 1.3a request) 775 776 Authorization: Digest username="testuser", realm="testrealm", \ 777 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca" 778 779 Digest parameters are all quoted strings. Username which is provided by 780 the user will need double quotes and backslashes within it escaped. For 781 the other fields, this shouldn't be an issue. realm, nonce, and opaque 782 are copied as is from the server, escapes and all. cnonce is generated 783 with web-safe characters. uri is already percent encoded. nc is 8 hex 784 characters. algorithm and qop with standard values only contain web-safe 785 characters. 786 */ 787 userp_quoted = auth_digest_string_quoted(userp); 788 if(!userp_quoted) 789 return CURLE_OUT_OF_MEMORY; 790 791 if(digest->qop) { 792 response = aprintf("username=\"%s\", " 793 "realm=\"%s\", " 794 "nonce=\"%s\", " 795 "uri=\"%s\", " 796 "cnonce=\"%s\", " 797 "nc=%08x, " 798 "qop=%s, " 799 "response=\"%s\"", 800 userp_quoted, 801 digest->realm, 802 digest->nonce, 803 uripath, 804 digest->cnonce, 805 digest->nc, 806 digest->qop, 807 request_digest); 808 809 if(Curl_raw_equal(digest->qop, "auth")) 810 digest->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0 811 padded which tells to the server how many times you are 812 using the same nonce in the qop=auth mode */ 813 } 814 else { 815 response = aprintf("username=\"%s\", " 816 "realm=\"%s\", " 817 "nonce=\"%s\", " 818 "uri=\"%s\", " 819 "response=\"%s\"", 820 userp_quoted, 821 digest->realm, 822 digest->nonce, 823 uripath, 824 request_digest); 825 } 826 free(userp_quoted); 827 if(!response) 828 return CURLE_OUT_OF_MEMORY; 829 830 /* Add the optional fields */ 831 if(digest->opaque) { 832 /* Append the opaque */ 833 tmp = aprintf("%s, opaque=\"%s\"", response, digest->opaque); 834 free(response); 835 if(!tmp) 836 return CURLE_OUT_OF_MEMORY; 837 838 response = tmp; 839 } 840 841 if(digest->algorithm) { 842 /* Append the algorithm */ 843 tmp = aprintf("%s, algorithm=\"%s\"", response, digest->algorithm); 844 free(response); 845 if(!tmp) 846 return CURLE_OUT_OF_MEMORY; 847 848 response = tmp; 849 } 850 851 /* Return the output */ 852 *outptr = response; 853 *outlen = strlen(response); 854 855 return CURLE_OK; 856 } 857 858 /* 859 * Curl_auth_digest_cleanup() 860 * 861 * This is used to clean up the digest specific data. 862 * 863 * Parameters: 864 * 865 * digest [in/out] - The digest data struct being cleaned up. 866 * 867 */ 868 void Curl_auth_digest_cleanup(struct digestdata *digest) 869 { 870 Curl_safefree(digest->nonce); 871 Curl_safefree(digest->cnonce); 872 Curl_safefree(digest->realm); 873 Curl_safefree(digest->opaque); 874 Curl_safefree(digest->qop); 875 Curl_safefree(digest->algorithm); 876 877 digest->nc = 0; 878 digest->algo = CURLDIGESTALGO_MD5; /* default algorithm */ 879 digest->stale = FALSE; /* default means normal, not stale */ 880 } 881 #endif /* !USE_WINDOWS_SSPI */ 882 883 #endif /* CURL_DISABLE_CRYPTO_AUTH */ 884