digest.c (31424B)
1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) Daniel Stenberg, <daniel@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.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 * SPDX-License-Identifier: curl 22 * 23 * RFC2831 DIGEST-MD5 authentication 24 * RFC7616 DIGEST-SHA256, DIGEST-SHA512-256 authentication 25 * 26 ***************************************************************************/ 27 28 #include "../curl_setup.h" 29 30 #ifndef CURL_DISABLE_DIGEST_AUTH 31 32 #include <curl/curl.h> 33 34 #include "vauth.h" 35 #include "digest.h" 36 #include "../urldata.h" 37 #include "../curlx/base64.h" 38 #include "../curl_hmac.h" 39 #include "../curl_md5.h" 40 #include "../curl_sha256.h" 41 #include "../curl_sha512_256.h" 42 #include "../vtls/vtls.h" 43 #include "../curlx/warnless.h" 44 #include "../curlx/strparse.h" 45 #include "../curl_printf.h" 46 #include "../rand.h" 47 48 /* The last #include files should be: */ 49 #include "../curl_memory.h" 50 #include "../memdebug.h" 51 52 #ifndef USE_WINDOWS_SSPI 53 #define SESSION_ALGO 1 /* for algos with this bit set */ 54 55 #define ALGO_MD5 0 56 #define ALGO_MD5SESS (ALGO_MD5 | SESSION_ALGO) 57 #define ALGO_SHA256 2 58 #define ALGO_SHA256SESS (ALGO_SHA256 | SESSION_ALGO) 59 #define ALGO_SHA512_256 4 60 #define ALGO_SHA512_256SESS (ALGO_SHA512_256 | SESSION_ALGO) 61 62 #define DIGEST_QOP_VALUE_AUTH (1 << 0) 63 #define DIGEST_QOP_VALUE_AUTH_INT (1 << 1) 64 #define DIGEST_QOP_VALUE_AUTH_CONF (1 << 2) 65 66 #define DIGEST_QOP_VALUE_STRING_AUTH "auth" 67 #define DIGEST_QOP_VALUE_STRING_AUTH_INT "auth-int" 68 #define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf" 69 #endif 70 71 bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, 72 const char **endptr) 73 { 74 int c; 75 bool starts_with_quote = FALSE; 76 bool escape = FALSE; 77 78 for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--);) 79 *value++ = *str++; 80 *value = 0; 81 82 if('=' != *str++) 83 /* eek, no match */ 84 return FALSE; 85 86 if('\"' == *str) { 87 /* This starts with a quote so it must end with one as well! */ 88 str++; 89 starts_with_quote = TRUE; 90 } 91 92 for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) { 93 if(!escape) { 94 switch(*str) { 95 case '\\': 96 if(starts_with_quote) { 97 /* the start of an escaped quote */ 98 escape = TRUE; 99 continue; 100 } 101 break; 102 103 case ',': 104 if(!starts_with_quote) { 105 /* This signals the end of the content if we did not get a starting 106 quote and then we do "sloppy" parsing */ 107 c = 0; /* the end */ 108 continue; 109 } 110 break; 111 112 case '\r': 113 case '\n': 114 /* end of string */ 115 if(starts_with_quote) 116 return FALSE; /* No closing quote */ 117 c = 0; 118 continue; 119 120 case '\"': 121 if(starts_with_quote) { 122 /* end of string */ 123 c = 0; 124 continue; 125 } 126 else 127 return FALSE; 128 } 129 } 130 131 escape = FALSE; 132 *content++ = *str; 133 } 134 if(escape) 135 return FALSE; /* No character after backslash */ 136 137 *content = 0; 138 *endptr = str; 139 140 return TRUE; 141 } 142 143 #ifndef USE_WINDOWS_SSPI 144 /* Convert MD5 chunk to RFC2617 (section 3.1.3) -suitable ASCII string */ 145 static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */ 146 unsigned char *dest) /* 33 bytes */ 147 { 148 int i; 149 for(i = 0; i < 16; i++) 150 msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]); 151 } 152 153 /* Convert sha256 or SHA-512/256 chunk to RFC7616 -suitable ASCII string */ 154 static void auth_digest_sha256_to_ascii(unsigned char *source, /* 32 bytes */ 155 unsigned char *dest) /* 65 bytes */ 156 { 157 int i; 158 for(i = 0; i < 32; i++) 159 msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]); 160 } 161 162 /* Perform quoted-string escaping as described in RFC2616 and its errata */ 163 static char *auth_digest_string_quoted(const char *source) 164 { 165 char *dest; 166 const char *s = source; 167 size_t n = 1; /* null-terminator */ 168 169 /* Calculate size needed */ 170 while(*s) { 171 ++n; 172 if(*s == '"' || *s == '\\') { 173 ++n; 174 } 175 ++s; 176 } 177 178 dest = malloc(n); 179 if(dest) { 180 char *d = dest; 181 s = source; 182 while(*s) { 183 if(*s == '"' || *s == '\\') { 184 *d++ = '\\'; 185 } 186 *d++ = *s++; 187 } 188 *d = '\0'; 189 } 190 191 return dest; 192 } 193 194 /* Retrieves the value for a corresponding key from the challenge string 195 * returns TRUE if the key could be found, FALSE if it does not exists 196 */ 197 static bool auth_digest_get_key_value(const char *chlg, 198 const char *key, 199 char *value, 200 size_t max_val_len, 201 char end_char) 202 { 203 char *find_pos; 204 size_t i; 205 206 find_pos = strstr(chlg, key); 207 if(!find_pos) 208 return FALSE; 209 210 find_pos += strlen(key); 211 212 for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i) 213 value[i] = *find_pos++; 214 value[i] = '\0'; 215 216 return TRUE; 217 } 218 219 static CURLcode auth_digest_get_qop_values(const char *options, int *value) 220 { 221 struct Curl_str out; 222 /* Initialise the output */ 223 *value = 0; 224 225 while(!curlx_str_until(&options, &out, 32, ',')) { 226 if(curlx_str_casecompare(&out, DIGEST_QOP_VALUE_STRING_AUTH)) 227 *value |= DIGEST_QOP_VALUE_AUTH; 228 else if(curlx_str_casecompare(&out, DIGEST_QOP_VALUE_STRING_AUTH_INT)) 229 *value |= DIGEST_QOP_VALUE_AUTH_INT; 230 else if(curlx_str_casecompare(&out, DIGEST_QOP_VALUE_STRING_AUTH_CONF)) 231 *value |= DIGEST_QOP_VALUE_AUTH_CONF; 232 if(curlx_str_single(&options, ',')) 233 break; 234 } 235 236 return CURLE_OK; 237 } 238 239 /* 240 * auth_decode_digest_md5_message() 241 * 242 * This is used internally to decode an already encoded DIGEST-MD5 challenge 243 * message into the separate attributes. 244 * 245 * Parameters: 246 * 247 * chlgref [in] - The challenge message. 248 * nonce [in/out] - The buffer where the nonce will be stored. 249 * nlen [in] - The length of the nonce buffer. 250 * realm [in/out] - The buffer where the realm will be stored. 251 * rlen [in] - The length of the realm buffer. 252 * alg [in/out] - The buffer where the algorithm will be stored. 253 * alen [in] - The length of the algorithm buffer. 254 * qop [in/out] - The buffer where the qop-options will be stored. 255 * qlen [in] - The length of the qop buffer. 256 * 257 * Returns CURLE_OK on success. 258 */ 259 static CURLcode auth_decode_digest_md5_message(const struct bufref *chlgref, 260 char *nonce, size_t nlen, 261 char *realm, size_t rlen, 262 char *alg, size_t alen, 263 char *qop, size_t qlen) 264 { 265 const char *chlg = (const char *) Curl_bufref_ptr(chlgref); 266 267 /* Ensure we have a valid challenge message */ 268 if(!Curl_bufref_len(chlgref)) 269 return CURLE_BAD_CONTENT_ENCODING; 270 271 /* Retrieve nonce string from the challenge */ 272 if(!auth_digest_get_key_value(chlg, "nonce=\"", nonce, nlen, '\"')) 273 return CURLE_BAD_CONTENT_ENCODING; 274 275 /* Retrieve realm string from the challenge */ 276 if(!auth_digest_get_key_value(chlg, "realm=\"", realm, rlen, '\"')) { 277 /* Challenge does not have a realm, set empty string [RFC2831] page 6 */ 278 *realm = '\0'; 279 } 280 281 /* Retrieve algorithm string from the challenge */ 282 if(!auth_digest_get_key_value(chlg, "algorithm=", alg, alen, ',')) 283 return CURLE_BAD_CONTENT_ENCODING; 284 285 /* Retrieve qop-options string from the challenge */ 286 if(!auth_digest_get_key_value(chlg, "qop=\"", qop, qlen, '\"')) 287 return CURLE_BAD_CONTENT_ENCODING; 288 289 return CURLE_OK; 290 } 291 292 /* 293 * Curl_auth_is_digest_supported() 294 * 295 * This is used to evaluate if DIGEST is supported. 296 * 297 * Parameters: None 298 * 299 * Returns TRUE as DIGEST as handled by libcurl. 300 */ 301 bool Curl_auth_is_digest_supported(void) 302 { 303 return TRUE; 304 } 305 306 /* 307 * Curl_auth_create_digest_md5_message() 308 * 309 * This is used to generate an already encoded DIGEST-MD5 response message 310 * ready for sending to the recipient. 311 * 312 * Parameters: 313 * 314 * data [in] - The session handle. 315 * chlg [in] - The challenge message. 316 * userp [in] - The username. 317 * passwdp [in] - The user's password. 318 * service [in] - The service type such as http, smtp, pop or imap. 319 * out [out] - The result storage. 320 * 321 * Returns CURLE_OK on success. 322 */ 323 CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, 324 const struct bufref *chlg, 325 const char *userp, 326 const char *passwdp, 327 const char *service, 328 struct bufref *out) 329 { 330 size_t i; 331 struct MD5_context *ctxt; 332 char *response = NULL; 333 unsigned char digest[MD5_DIGEST_LEN]; 334 char HA1_hex[2 * MD5_DIGEST_LEN + 1]; 335 char HA2_hex[2 * MD5_DIGEST_LEN + 1]; 336 char resp_hash_hex[2 * MD5_DIGEST_LEN + 1]; 337 char nonce[64]; 338 char realm[128]; 339 char algorithm[64]; 340 char qop_options[64]; 341 int qop_values; 342 char cnonce[33]; 343 char nonceCount[] = "00000001"; 344 char method[] = "AUTHENTICATE"; 345 char qop[] = DIGEST_QOP_VALUE_STRING_AUTH; 346 char *spn = NULL; 347 348 /* Decode the challenge message */ 349 CURLcode result = auth_decode_digest_md5_message(chlg, 350 nonce, sizeof(nonce), 351 realm, sizeof(realm), 352 algorithm, 353 sizeof(algorithm), 354 qop_options, 355 sizeof(qop_options)); 356 if(result) 357 return result; 358 359 /* We only support md5 sessions */ 360 if(strcmp(algorithm, "md5-sess") != 0) 361 return CURLE_BAD_CONTENT_ENCODING; 362 363 /* Get the qop-values from the qop-options */ 364 result = auth_digest_get_qop_values(qop_options, &qop_values); 365 if(result) 366 return result; 367 368 /* We only support auth quality-of-protection */ 369 if(!(qop_values & DIGEST_QOP_VALUE_AUTH)) 370 return CURLE_BAD_CONTENT_ENCODING; 371 372 /* Generate 32 random hex chars, 32 bytes + 1 null-termination */ 373 result = Curl_rand_hex(data, (unsigned char *)cnonce, sizeof(cnonce)); 374 if(result) 375 return result; 376 377 /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */ 378 ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); 379 if(!ctxt) 380 return CURLE_OUT_OF_MEMORY; 381 382 Curl_MD5_update(ctxt, (const unsigned char *) userp, 383 curlx_uztoui(strlen(userp))); 384 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 385 Curl_MD5_update(ctxt, (const unsigned char *) realm, 386 curlx_uztoui(strlen(realm))); 387 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 388 Curl_MD5_update(ctxt, (const unsigned char *) passwdp, 389 curlx_uztoui(strlen(passwdp))); 390 Curl_MD5_final(ctxt, digest); 391 392 ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); 393 if(!ctxt) 394 return CURLE_OUT_OF_MEMORY; 395 396 Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN); 397 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 398 Curl_MD5_update(ctxt, (const unsigned char *) nonce, 399 curlx_uztoui(strlen(nonce))); 400 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 401 Curl_MD5_update(ctxt, (const unsigned char *) cnonce, 402 curlx_uztoui(strlen(cnonce))); 403 Curl_MD5_final(ctxt, digest); 404 405 /* Convert calculated 16 octet hex into 32 bytes string */ 406 for(i = 0; i < MD5_DIGEST_LEN; i++) 407 msnprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]); 408 409 /* Generate our SPN */ 410 spn = Curl_auth_build_spn(service, data->conn->host.name, NULL); 411 if(!spn) 412 return CURLE_OUT_OF_MEMORY; 413 414 /* Calculate H(A2) */ 415 ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); 416 if(!ctxt) { 417 free(spn); 418 419 return CURLE_OUT_OF_MEMORY; 420 } 421 422 Curl_MD5_update(ctxt, (const unsigned char *) method, 423 curlx_uztoui(strlen(method))); 424 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 425 Curl_MD5_update(ctxt, (const unsigned char *) spn, 426 curlx_uztoui(strlen(spn))); 427 Curl_MD5_final(ctxt, digest); 428 429 for(i = 0; i < MD5_DIGEST_LEN; i++) 430 msnprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]); 431 432 /* Now calculate the response hash */ 433 ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); 434 if(!ctxt) { 435 free(spn); 436 437 return CURLE_OUT_OF_MEMORY; 438 } 439 440 Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN); 441 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 442 Curl_MD5_update(ctxt, (const unsigned char *) nonce, 443 curlx_uztoui(strlen(nonce))); 444 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 445 446 Curl_MD5_update(ctxt, (const unsigned char *) nonceCount, 447 curlx_uztoui(strlen(nonceCount))); 448 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 449 Curl_MD5_update(ctxt, (const unsigned char *) cnonce, 450 curlx_uztoui(strlen(cnonce))); 451 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 452 Curl_MD5_update(ctxt, (const unsigned char *) qop, 453 curlx_uztoui(strlen(qop))); 454 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 455 456 Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN); 457 Curl_MD5_final(ctxt, digest); 458 459 for(i = 0; i < MD5_DIGEST_LEN; i++) 460 msnprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]); 461 462 /* Generate the response */ 463 response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\"," 464 "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s," 465 "qop=%s", 466 userp, realm, nonce, 467 cnonce, nonceCount, spn, resp_hash_hex, qop); 468 free(spn); 469 if(!response) 470 return CURLE_OUT_OF_MEMORY; 471 472 /* Return the response. */ 473 Curl_bufref_set(out, response, strlen(response), curl_free); 474 return result; 475 } 476 477 /* 478 * Curl_auth_decode_digest_http_message() 479 * 480 * This is used to decode an HTTP DIGEST challenge message into the separate 481 * attributes. 482 * 483 * Parameters: 484 * 485 * chlg [in] - The challenge message. 486 * digest [in/out] - The digest data struct being used and modified. 487 * 488 * Returns CURLE_OK on success. 489 */ 490 CURLcode Curl_auth_decode_digest_http_message(const char *chlg, 491 struct digestdata *digest) 492 { 493 bool before = FALSE; /* got a nonce before */ 494 495 /* If we already have received a nonce, keep that in mind */ 496 if(digest->nonce) 497 before = TRUE; 498 499 /* Clean up any former leftovers and initialise to defaults */ 500 Curl_auth_digest_cleanup(digest); 501 502 for(;;) { 503 char value[DIGEST_MAX_VALUE_LENGTH]; 504 char content[DIGEST_MAX_CONTENT_LENGTH]; 505 506 /* Pass all additional spaces here */ 507 while(*chlg && ISBLANK(*chlg)) 508 chlg++; 509 510 /* Extract a value=content pair */ 511 if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) { 512 if(curl_strequal(value, "nonce")) { 513 free(digest->nonce); 514 digest->nonce = strdup(content); 515 if(!digest->nonce) 516 return CURLE_OUT_OF_MEMORY; 517 } 518 else if(curl_strequal(value, "stale")) { 519 if(curl_strequal(content, "true")) { 520 digest->stale = TRUE; 521 digest->nc = 1; /* we make a new nonce now */ 522 } 523 } 524 else if(curl_strequal(value, "realm")) { 525 free(digest->realm); 526 digest->realm = strdup(content); 527 if(!digest->realm) 528 return CURLE_OUT_OF_MEMORY; 529 } 530 else if(curl_strequal(value, "opaque")) { 531 free(digest->opaque); 532 digest->opaque = strdup(content); 533 if(!digest->opaque) 534 return CURLE_OUT_OF_MEMORY; 535 } 536 else if(curl_strequal(value, "qop")) { 537 const char *token = content; 538 struct Curl_str out; 539 bool foundAuth = FALSE; 540 bool foundAuthInt = FALSE; 541 /* Pass leading spaces */ 542 while(*token && ISBLANK(*token)) 543 token++; 544 while(!curlx_str_until(&token, &out, 32, ',')) { 545 if(curlx_str_casecompare(&out, DIGEST_QOP_VALUE_STRING_AUTH)) 546 foundAuth = TRUE; 547 else if(curlx_str_casecompare(&out, 548 DIGEST_QOP_VALUE_STRING_AUTH_INT)) 549 foundAuthInt = TRUE; 550 if(curlx_str_single(&token, ',')) 551 break; 552 while(*token && ISBLANK(*token)) 553 token++; 554 } 555 556 /* Select only auth or auth-int. Otherwise, ignore */ 557 if(foundAuth) { 558 free(digest->qop); 559 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH); 560 if(!digest->qop) 561 return CURLE_OUT_OF_MEMORY; 562 } 563 else if(foundAuthInt) { 564 free(digest->qop); 565 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT); 566 if(!digest->qop) 567 return CURLE_OUT_OF_MEMORY; 568 } 569 } 570 else if(curl_strequal(value, "algorithm")) { 571 free(digest->algorithm); 572 digest->algorithm = strdup(content); 573 if(!digest->algorithm) 574 return CURLE_OUT_OF_MEMORY; 575 576 if(curl_strequal(content, "MD5-sess")) 577 digest->algo = ALGO_MD5SESS; 578 else if(curl_strequal(content, "MD5")) 579 digest->algo = ALGO_MD5; 580 else if(curl_strequal(content, "SHA-256")) 581 digest->algo = ALGO_SHA256; 582 else if(curl_strequal(content, "SHA-256-SESS")) 583 digest->algo = ALGO_SHA256SESS; 584 else if(curl_strequal(content, "SHA-512-256")) { 585 #ifdef CURL_HAVE_SHA512_256 586 digest->algo = ALGO_SHA512_256; 587 #else /* ! CURL_HAVE_SHA512_256 */ 588 return CURLE_NOT_BUILT_IN; 589 #endif /* ! CURL_HAVE_SHA512_256 */ 590 } 591 else if(curl_strequal(content, "SHA-512-256-SESS")) { 592 #ifdef CURL_HAVE_SHA512_256 593 digest->algo = ALGO_SHA512_256SESS; 594 #else /* ! CURL_HAVE_SHA512_256 */ 595 return CURLE_NOT_BUILT_IN; 596 #endif /* ! CURL_HAVE_SHA512_256 */ 597 } 598 else 599 return CURLE_BAD_CONTENT_ENCODING; 600 } 601 else if(curl_strequal(value, "userhash")) { 602 if(curl_strequal(content, "true")) { 603 digest->userhash = TRUE; 604 } 605 } 606 else { 607 /* Unknown specifier, ignore it! */ 608 } 609 } 610 else 611 break; /* We are done here */ 612 613 /* Pass all additional spaces here */ 614 while(*chlg && ISBLANK(*chlg)) 615 chlg++; 616 617 /* Allow the list to be comma-separated */ 618 if(',' == *chlg) 619 chlg++; 620 } 621 622 /* We had a nonce since before, and we got another one now without 623 'stale=true'. This means we provided bad credentials in the previous 624 request */ 625 if(before && !digest->stale) 626 return CURLE_BAD_CONTENT_ENCODING; 627 628 /* We got this header without a nonce, that is a bad Digest line! */ 629 if(!digest->nonce) 630 return CURLE_BAD_CONTENT_ENCODING; 631 632 /* "<algo>-sess" protocol versions require "auth" or "auth-int" qop */ 633 if(!digest->qop && (digest->algo & SESSION_ALGO)) 634 return CURLE_BAD_CONTENT_ENCODING; 635 636 return CURLE_OK; 637 } 638 639 /* 640 * auth_create_digest_http_message() 641 * 642 * This is used to generate an HTTP DIGEST response message ready for sending 643 * to the recipient. 644 * 645 * Parameters: 646 * 647 * data [in] - The session handle. 648 * userp [in] - The username. 649 * passwdp [in] - The user's password. 650 * request [in] - The HTTP request. 651 * uripath [in] - The path of the HTTP uri. 652 * digest [in/out] - The digest data struct being used and modified. 653 * outptr [in/out] - The address where a pointer to newly allocated memory 654 * holding the result will be stored upon completion. 655 * outlen [out] - The length of the output message. 656 * 657 * Returns CURLE_OK on success. 658 */ 659 static CURLcode auth_create_digest_http_message( 660 struct Curl_easy *data, 661 const char *userp, 662 const char *passwdp, 663 const unsigned char *request, 664 const unsigned char *uripath, 665 struct digestdata *digest, 666 char **outptr, size_t *outlen, 667 void (*convert_to_ascii)(unsigned char *, unsigned char *), 668 CURLcode (*hash)(unsigned char *, const unsigned char *, 669 const size_t)) 670 { 671 CURLcode result; 672 unsigned char hashbuf[32]; /* 32 bytes/256 bits */ 673 unsigned char request_digest[65]; 674 unsigned char ha1[65]; /* 64 digits and 1 zero byte */ 675 unsigned char ha2[65]; /* 64 digits and 1 zero byte */ 676 char userh[65]; 677 char *cnonce = NULL; 678 size_t cnonce_sz = 0; 679 char *userp_quoted; 680 char *realm_quoted; 681 char *nonce_quoted; 682 char *response = NULL; 683 char *hashthis = NULL; 684 char *tmp = NULL; 685 686 memset(hashbuf, 0, sizeof(hashbuf)); 687 if(!digest->nc) 688 digest->nc = 1; 689 690 if(!digest->cnonce) { 691 char cnoncebuf[12]; 692 result = Curl_rand_bytes(data, 693 #ifdef DEBUGBUILD 694 TRUE, 695 #endif 696 (unsigned char *)cnoncebuf, 697 sizeof(cnoncebuf)); 698 if(result) 699 return result; 700 701 result = curlx_base64_encode(cnoncebuf, sizeof(cnoncebuf), 702 &cnonce, &cnonce_sz); 703 if(result) 704 return result; 705 706 digest->cnonce = cnonce; 707 } 708 709 if(digest->userhash) { 710 hashthis = aprintf("%s:%s", userp, digest->realm ? digest->realm : ""); 711 if(!hashthis) 712 return CURLE_OUT_OF_MEMORY; 713 714 result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); 715 free(hashthis); 716 if(result) 717 return result; 718 convert_to_ascii(hashbuf, (unsigned char *)userh); 719 } 720 721 /* 722 If the algorithm is "MD5" or unspecified (which then defaults to MD5): 723 724 A1 = unq(username-value) ":" unq(realm-value) ":" passwd 725 726 If the algorithm is "MD5-sess" then: 727 728 A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":" 729 unq(nonce-value) ":" unq(cnonce-value) 730 */ 731 732 hashthis = aprintf("%s:%s:%s", userp, digest->realm ? digest->realm : "", 733 passwdp); 734 if(!hashthis) 735 return CURLE_OUT_OF_MEMORY; 736 737 result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); 738 free(hashthis); 739 if(result) 740 return result; 741 convert_to_ascii(hashbuf, ha1); 742 743 if(digest->algo & SESSION_ALGO) { 744 /* nonce and cnonce are OUTSIDE the hash */ 745 tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce); 746 if(!tmp) 747 return CURLE_OUT_OF_MEMORY; 748 749 result = hash(hashbuf, (unsigned char *) tmp, strlen(tmp)); 750 free(tmp); 751 if(result) 752 return result; 753 convert_to_ascii(hashbuf, ha1); 754 } 755 756 /* 757 If the "qop" directive's value is "auth" or is unspecified, then A2 is: 758 759 A2 = Method ":" digest-uri-value 760 761 If the "qop" value is "auth-int", then A2 is: 762 763 A2 = Method ":" digest-uri-value ":" H(entity-body) 764 765 (The "Method" value is the HTTP request method as specified in section 766 5.1.1 of RFC 2616) 767 */ 768 769 hashthis = aprintf("%s:%s", request, uripath); 770 if(!hashthis) 771 return CURLE_OUT_OF_MEMORY; 772 773 if(digest->qop && curl_strequal(digest->qop, "auth-int")) { 774 /* We do not support auth-int for PUT or POST */ 775 char hashed[65]; 776 char *hashthis2; 777 778 result = hash(hashbuf, (const unsigned char *)"", 0); 779 if(result) { 780 free(hashthis); 781 return result; 782 } 783 convert_to_ascii(hashbuf, (unsigned char *)hashed); 784 785 hashthis2 = aprintf("%s:%s", hashthis, hashed); 786 free(hashthis); 787 hashthis = hashthis2; 788 } 789 790 if(!hashthis) 791 return CURLE_OUT_OF_MEMORY; 792 793 result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); 794 free(hashthis); 795 if(result) 796 return result; 797 convert_to_ascii(hashbuf, ha2); 798 799 if(digest->qop) { 800 hashthis = aprintf("%s:%s:%08x:%s:%s:%s", ha1, digest->nonce, digest->nc, 801 digest->cnonce, digest->qop, ha2); 802 } 803 else { 804 hashthis = aprintf("%s:%s:%s", ha1, digest->nonce, ha2); 805 } 806 807 if(!hashthis) 808 return CURLE_OUT_OF_MEMORY; 809 810 result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); 811 free(hashthis); 812 if(result) 813 return result; 814 convert_to_ascii(hashbuf, request_digest); 815 816 /* For test case 64 (snooped from a Mozilla 1.3a request) 817 818 Authorization: Digest username="testuser", realm="testrealm", \ 819 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca" 820 821 Digest parameters are all quoted strings. Username which is provided by 822 the user will need double quotes and backslashes within it escaped. 823 realm, nonce, and opaque will need backslashes as well as they were 824 de-escaped when copied from request header. cnonce is generated with 825 web-safe characters. uri is already percent encoded. nc is 8 hex 826 characters. algorithm and qop with standard values only contain web-safe 827 characters. 828 */ 829 userp_quoted = auth_digest_string_quoted(digest->userhash ? userh : userp); 830 if(!userp_quoted) 831 return CURLE_OUT_OF_MEMORY; 832 if(digest->realm) 833 realm_quoted = auth_digest_string_quoted(digest->realm); 834 else { 835 realm_quoted = malloc(1); 836 if(realm_quoted) 837 realm_quoted[0] = 0; 838 } 839 if(!realm_quoted) { 840 free(userp_quoted); 841 return CURLE_OUT_OF_MEMORY; 842 } 843 nonce_quoted = auth_digest_string_quoted(digest->nonce); 844 if(!nonce_quoted) { 845 free(realm_quoted); 846 free(userp_quoted); 847 return CURLE_OUT_OF_MEMORY; 848 } 849 850 if(digest->qop) { 851 response = aprintf("username=\"%s\", " 852 "realm=\"%s\", " 853 "nonce=\"%s\", " 854 "uri=\"%s\", " 855 "cnonce=\"%s\", " 856 "nc=%08x, " 857 "qop=%s, " 858 "response=\"%s\"", 859 userp_quoted, 860 realm_quoted, 861 nonce_quoted, 862 uripath, 863 digest->cnonce, 864 digest->nc, 865 digest->qop, 866 request_digest); 867 868 /* Increment nonce-count to use another nc value for the next request */ 869 digest->nc++; 870 } 871 else { 872 response = aprintf("username=\"%s\", " 873 "realm=\"%s\", " 874 "nonce=\"%s\", " 875 "uri=\"%s\", " 876 "response=\"%s\"", 877 userp_quoted, 878 realm_quoted, 879 nonce_quoted, 880 uripath, 881 request_digest); 882 } 883 free(nonce_quoted); 884 free(realm_quoted); 885 free(userp_quoted); 886 if(!response) 887 return CURLE_OUT_OF_MEMORY; 888 889 /* Add the optional fields */ 890 if(digest->opaque) { 891 char *opaque_quoted; 892 /* Append the opaque */ 893 opaque_quoted = auth_digest_string_quoted(digest->opaque); 894 if(!opaque_quoted) { 895 free(response); 896 return CURLE_OUT_OF_MEMORY; 897 } 898 tmp = aprintf("%s, opaque=\"%s\"", response, opaque_quoted); 899 free(response); 900 free(opaque_quoted); 901 if(!tmp) 902 return CURLE_OUT_OF_MEMORY; 903 904 response = tmp; 905 } 906 907 if(digest->algorithm) { 908 /* Append the algorithm */ 909 tmp = aprintf("%s, algorithm=%s", response, digest->algorithm); 910 free(response); 911 if(!tmp) 912 return CURLE_OUT_OF_MEMORY; 913 914 response = tmp; 915 } 916 917 if(digest->userhash) { 918 /* Append the userhash */ 919 tmp = aprintf("%s, userhash=true", response); 920 free(response); 921 if(!tmp) 922 return CURLE_OUT_OF_MEMORY; 923 924 response = tmp; 925 } 926 927 /* Return the output */ 928 *outptr = response; 929 *outlen = strlen(response); 930 931 return CURLE_OK; 932 } 933 934 /* 935 * Curl_auth_create_digest_http_message() 936 * 937 * This is used to generate an HTTP DIGEST response message ready for sending 938 * to the recipient. 939 * 940 * Parameters: 941 * 942 * data [in] - The session handle. 943 * userp [in] - The username. 944 * passwdp [in] - The user's password. 945 * request [in] - The HTTP request. 946 * uripath [in] - The path of the HTTP uri. 947 * digest [in/out] - The digest data struct being used and modified. 948 * outptr [in/out] - The address where a pointer to newly allocated memory 949 * holding the result will be stored upon completion. 950 * outlen [out] - The length of the output message. 951 * 952 * Returns CURLE_OK on success. 953 */ 954 CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, 955 const char *userp, 956 const char *passwdp, 957 const unsigned char *request, 958 const unsigned char *uripath, 959 struct digestdata *digest, 960 char **outptr, size_t *outlen) 961 { 962 if(digest->algo <= ALGO_MD5SESS) 963 return auth_create_digest_http_message(data, userp, passwdp, 964 request, uripath, digest, 965 outptr, outlen, 966 auth_digest_md5_to_ascii, 967 Curl_md5it); 968 969 if(digest->algo <= ALGO_SHA256SESS) 970 return auth_create_digest_http_message(data, userp, passwdp, 971 request, uripath, digest, 972 outptr, outlen, 973 auth_digest_sha256_to_ascii, 974 Curl_sha256it); 975 #ifdef CURL_HAVE_SHA512_256 976 if(digest->algo <= ALGO_SHA512_256SESS) 977 return auth_create_digest_http_message(data, userp, passwdp, 978 request, uripath, digest, 979 outptr, outlen, 980 auth_digest_sha256_to_ascii, 981 Curl_sha512_256it); 982 #endif /* CURL_HAVE_SHA512_256 */ 983 984 /* Should be unreachable */ 985 return CURLE_BAD_CONTENT_ENCODING; 986 } 987 988 /* 989 * Curl_auth_digest_cleanup() 990 * 991 * This is used to clean up the digest specific data. 992 * 993 * Parameters: 994 * 995 * digest [in/out] - The digest data struct being cleaned up. 996 * 997 */ 998 void Curl_auth_digest_cleanup(struct digestdata *digest) 999 { 1000 Curl_safefree(digest->nonce); 1001 Curl_safefree(digest->cnonce); 1002 Curl_safefree(digest->realm); 1003 Curl_safefree(digest->opaque); 1004 Curl_safefree(digest->qop); 1005 Curl_safefree(digest->algorithm); 1006 1007 digest->nc = 0; 1008 digest->algo = ALGO_MD5; /* default algorithm */ 1009 digest->stale = FALSE; /* default means normal, not stale */ 1010 digest->userhash = FALSE; 1011 } 1012 #endif /* !USE_WINDOWS_SSPI */ 1013 1014 #endif /* !CURL_DISABLE_DIGEST_AUTH */