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/vauth.h" 35#include "vauth/digest.h" 36#include "urldata.h" 37#include "curl_base64.h" 38#include "curl_hmac.h" 39#include "curl_md5.h" 40#include "curl_sha256.h" 41#include "vtls/vtls.h" 42#include "warnless.h" 43#include "strtok.h" 44#include "strcase.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#define SESSION_ALGO 1 /* for algos with this bit set */ 53 54#define ALGO_MD5 0 55#define ALGO_MD5SESS (ALGO_MD5 | SESSION_ALGO) 56#define ALGO_SHA256 2 57#define ALGO_SHA256SESS (ALGO_SHA256 | SESSION_ALGO) 58#define ALGO_SHA512_256 4 59#define ALGO_SHA512_256SESS (ALGO_SHA512_256 | SESSION_ALGO) 60 61#if !defined(USE_WINDOWS_SSPI) 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 71bool 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 didn't 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#if !defined(USE_WINDOWS_SSPI) 144/* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string */ 145static 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 chunk to RFC7616 -suitable ascii string */ 154static 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 */ 163static 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 */ 197static 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 219static CURLcode auth_digest_get_qop_values(const char *options, int *value) 220{ 221 char *tmp; 222 char *token; 223 char *tok_buf = NULL; 224 225 /* Initialise the output */ 226 *value = 0; 227 228 /* Tokenise the list of qop values. Use a temporary clone of the buffer since 229 strtok_r() ruins it. */ 230 tmp = strdup(options); 231 if(!tmp) 232 return CURLE_OUT_OF_MEMORY; 233 234 token = strtok_r(tmp, ",", &tok_buf); 235 while(token) { 236 if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) 237 *value |= DIGEST_QOP_VALUE_AUTH; 238 else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) 239 *value |= DIGEST_QOP_VALUE_AUTH_INT; 240 else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF)) 241 *value |= DIGEST_QOP_VALUE_AUTH_CONF; 242 243 token = strtok_r(NULL, ",", &tok_buf); 244 } 245 246 free(tmp); 247 248 return CURLE_OK; 249} 250 251/* 252 * auth_decode_digest_md5_message() 253 * 254 * This is used internally to decode an already encoded DIGEST-MD5 challenge 255 * message into the separate attributes. 256 * 257 * Parameters: 258 * 259 * chlgref [in] - The challenge message. 260 * nonce [in/out] - The buffer where the nonce will be stored. 261 * nlen [in] - The length of the nonce buffer. 262 * realm [in/out] - The buffer where the realm will be stored. 263 * rlen [in] - The length of the realm buffer. 264 * alg [in/out] - The buffer where the algorithm will be stored. 265 * alen [in] - The length of the algorithm buffer. 266 * qop [in/out] - The buffer where the qop-options will be stored. 267 * qlen [in] - The length of the qop buffer. 268 * 269 * Returns CURLE_OK on success. 270 */ 271static CURLcode auth_decode_digest_md5_message(const struct bufref *chlgref, 272 char *nonce, size_t nlen, 273 char *realm, size_t rlen, 274 char *alg, size_t alen, 275 char *qop, size_t qlen) 276{ 277 const char *chlg = (const char *) Curl_bufref_ptr(chlgref); 278 279 /* Ensure we have a valid challenge message */ 280 if(!Curl_bufref_len(chlgref)) 281 return CURLE_BAD_CONTENT_ENCODING; 282 283 /* Retrieve nonce string from the challenge */ 284 if(!auth_digest_get_key_value(chlg, "nonce=\"", nonce, nlen, '\"')) 285 return CURLE_BAD_CONTENT_ENCODING; 286 287 /* Retrieve realm string from the challenge */ 288 if(!auth_digest_get_key_value(chlg, "realm=\"", realm, rlen, '\"')) { 289 /* Challenge does not have a realm, set empty string [RFC2831] page 6 */ 290 strcpy(realm, ""); 291 } 292 293 /* Retrieve algorithm string from the challenge */ 294 if(!auth_digest_get_key_value(chlg, "algorithm=", alg, alen, ',')) 295 return CURLE_BAD_CONTENT_ENCODING; 296 297 /* Retrieve qop-options string from the challenge */ 298 if(!auth_digest_get_key_value(chlg, "qop=\"", qop, qlen, '\"')) 299 return CURLE_BAD_CONTENT_ENCODING; 300 301 return CURLE_OK; 302} 303 304/* 305 * Curl_auth_is_digest_supported() 306 * 307 * This is used to evaluate if DIGEST is supported. 308 * 309 * Parameters: None 310 * 311 * Returns TRUE as DIGEST as handled by libcurl. 312 */ 313bool Curl_auth_is_digest_supported(void) 314{ 315 return TRUE; 316} 317 318/* 319 * Curl_auth_create_digest_md5_message() 320 * 321 * This is used to generate an already encoded DIGEST-MD5 response message 322 * ready for sending to the recipient. 323 * 324 * Parameters: 325 * 326 * data [in] - The session handle. 327 * chlg [in] - The challenge message. 328 * userp [in] - The user name. 329 * passwdp [in] - The user's password. 330 * service [in] - The service type such as http, smtp, pop or imap. 331 * out [out] - The result storage. 332 * 333 * Returns CURLE_OK on success. 334 */ 335CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, 336 const struct bufref *chlg, 337 const char *userp, 338 const char *passwdp, 339 const char *service, 340 struct bufref *out) 341{ 342 size_t i; 343 struct MD5_context *ctxt; 344 char *response = NULL; 345 unsigned char digest[MD5_DIGEST_LEN]; 346 char HA1_hex[2 * MD5_DIGEST_LEN + 1]; 347 char HA2_hex[2 * MD5_DIGEST_LEN + 1]; 348 char resp_hash_hex[2 * MD5_DIGEST_LEN + 1]; 349 char nonce[64]; 350 char realm[128]; 351 char algorithm[64]; 352 char qop_options[64]; 353 int qop_values; 354 char cnonce[33]; 355 char nonceCount[] = "00000001"; 356 char method[] = "AUTHENTICATE"; 357 char qop[] = DIGEST_QOP_VALUE_STRING_AUTH; 358 char *spn = NULL; 359 360 /* Decode the challenge message */ 361 CURLcode result = auth_decode_digest_md5_message(chlg, 362 nonce, sizeof(nonce), 363 realm, sizeof(realm), 364 algorithm, 365 sizeof(algorithm), 366 qop_options, 367 sizeof(qop_options)); 368 if(result) 369 return result; 370 371 /* We only support md5 sessions */ 372 if(strcmp(algorithm, "md5-sess") != 0) 373 return CURLE_BAD_CONTENT_ENCODING; 374 375 /* Get the qop-values from the qop-options */ 376 result = auth_digest_get_qop_values(qop_options, &qop_values); 377 if(result) 378 return result; 379 380 /* We only support auth quality-of-protection */ 381 if(!(qop_values & DIGEST_QOP_VALUE_AUTH)) 382 return CURLE_BAD_CONTENT_ENCODING; 383 384 /* Generate 32 random hex chars, 32 bytes + 1 null-termination */ 385 result = Curl_rand_hex(data, (unsigned char *)cnonce, sizeof(cnonce)); 386 if(result) 387 return result; 388 389 /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */ 390 ctxt = Curl_MD5_init(Curl_DIGEST_MD5); 391 if(!ctxt) 392 return CURLE_OUT_OF_MEMORY; 393 394 Curl_MD5_update(ctxt, (const unsigned char *) userp, 395 curlx_uztoui(strlen(userp))); 396 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 397 Curl_MD5_update(ctxt, (const unsigned char *) realm, 398 curlx_uztoui(strlen(realm))); 399 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 400 Curl_MD5_update(ctxt, (const unsigned char *) passwdp, 401 curlx_uztoui(strlen(passwdp))); 402 Curl_MD5_final(ctxt, digest); 403 404 ctxt = Curl_MD5_init(Curl_DIGEST_MD5); 405 if(!ctxt) 406 return CURLE_OUT_OF_MEMORY; 407 408 Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN); 409 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 410 Curl_MD5_update(ctxt, (const unsigned char *) nonce, 411 curlx_uztoui(strlen(nonce))); 412 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 413 Curl_MD5_update(ctxt, (const unsigned char *) cnonce, 414 curlx_uztoui(strlen(cnonce))); 415 Curl_MD5_final(ctxt, digest); 416 417 /* Convert calculated 16 octet hex into 32 bytes string */ 418 for(i = 0; i < MD5_DIGEST_LEN; i++) 419 msnprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]); 420 421 /* Generate our SPN */ 422 spn = Curl_auth_build_spn(service, data->conn->host.name, NULL); 423 if(!spn) 424 return CURLE_OUT_OF_MEMORY; 425 426 /* Calculate H(A2) */ 427 ctxt = Curl_MD5_init(Curl_DIGEST_MD5); 428 if(!ctxt) { 429 free(spn); 430 431 return CURLE_OUT_OF_MEMORY; 432 } 433 434 Curl_MD5_update(ctxt, (const unsigned char *) method, 435 curlx_uztoui(strlen(method))); 436 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 437 Curl_MD5_update(ctxt, (const unsigned char *) spn, 438 curlx_uztoui(strlen(spn))); 439 Curl_MD5_final(ctxt, digest); 440 441 for(i = 0; i < MD5_DIGEST_LEN; i++) 442 msnprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]); 443 444 /* Now calculate the response hash */ 445 ctxt = Curl_MD5_init(Curl_DIGEST_MD5); 446 if(!ctxt) { 447 free(spn); 448 449 return CURLE_OUT_OF_MEMORY; 450 } 451 452 Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN); 453 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 454 Curl_MD5_update(ctxt, (const unsigned char *) nonce, 455 curlx_uztoui(strlen(nonce))); 456 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 457 458 Curl_MD5_update(ctxt, (const unsigned char *) nonceCount, 459 curlx_uztoui(strlen(nonceCount))); 460 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 461 Curl_MD5_update(ctxt, (const unsigned char *) cnonce, 462 curlx_uztoui(strlen(cnonce))); 463 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 464 Curl_MD5_update(ctxt, (const unsigned char *) qop, 465 curlx_uztoui(strlen(qop))); 466 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); 467 468 Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN); 469 Curl_MD5_final(ctxt, digest); 470 471 for(i = 0; i < MD5_DIGEST_LEN; i++) 472 msnprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]); 473 474 /* Generate the response */ 475 response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\"," 476 "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s," 477 "qop=%s", 478 userp, realm, nonce, 479 cnonce, nonceCount, spn, resp_hash_hex, qop); 480 free(spn); 481 if(!response) 482 return CURLE_OUT_OF_MEMORY; 483 484 /* Return the response. */ 485 Curl_bufref_set(out, response, strlen(response), curl_free); 486 return result; 487} 488 489/* 490 * Curl_auth_decode_digest_http_message() 491 * 492 * This is used to decode an HTTP DIGEST challenge message into the separate 493 * attributes. 494 * 495 * Parameters: 496 * 497 * chlg [in] - The challenge message. 498 * digest [in/out] - The digest data struct being used and modified. 499 * 500 * Returns CURLE_OK on success. 501 */ 502CURLcode Curl_auth_decode_digest_http_message(const char *chlg, 503 struct digestdata *digest) 504{ 505 bool before = FALSE; /* got a nonce before */ 506 bool foundAuth = FALSE; 507 bool foundAuthInt = FALSE; 508 char *token = NULL; 509 char *tmp = NULL; 510 511 /* If we already have received a nonce, keep that in mind */ 512 if(digest->nonce) 513 before = TRUE; 514 515 /* Clean up any former leftovers and initialise to defaults */ 516 Curl_auth_digest_cleanup(digest); 517 518 for(;;) { 519 char value[DIGEST_MAX_VALUE_LENGTH]; 520 char content[DIGEST_MAX_CONTENT_LENGTH]; 521 522 /* Pass all additional spaces here */ 523 while(*chlg && ISBLANK(*chlg)) 524 chlg++; 525 526 /* Extract a value=content pair */ 527 if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) { 528 if(strcasecompare(value, "nonce")) { 529 free(digest->nonce); 530 digest->nonce = strdup(content); 531 if(!digest->nonce) 532 return CURLE_OUT_OF_MEMORY; 533 } 534 else if(strcasecompare(value, "stale")) { 535 if(strcasecompare(content, "true")) { 536 digest->stale = TRUE; 537 digest->nc = 1; /* we make a new nonce now */ 538 } 539 } 540 else if(strcasecompare(value, "realm")) { 541 free(digest->realm); 542 digest->realm = strdup(content); 543 if(!digest->realm) 544 return CURLE_OUT_OF_MEMORY; 545 } 546 else if(strcasecompare(value, "opaque")) { 547 free(digest->opaque); 548 digest->opaque = strdup(content); 549 if(!digest->opaque) 550 return CURLE_OUT_OF_MEMORY; 551 } 552 else if(strcasecompare(value, "qop")) { 553 char *tok_buf = NULL; 554 /* Tokenize the list and choose auth if possible, use a temporary 555 clone of the buffer since strtok_r() ruins it */ 556 tmp = strdup(content); 557 if(!tmp) 558 return CURLE_OUT_OF_MEMORY; 559 560 token = strtok_r(tmp, ",", &tok_buf); 561 while(token) { 562 /* Pass additional spaces here */ 563 while(*token && ISBLANK(*token)) 564 token++; 565 if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) { 566 foundAuth = TRUE; 567 } 568 else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) { 569 foundAuthInt = TRUE; 570 } 571 token = strtok_r(NULL, ",", &tok_buf); 572 } 573 574 free(tmp); 575 576 /* Select only auth or auth-int. Otherwise, ignore */ 577 if(foundAuth) { 578 free(digest->qop); 579 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH); 580 if(!digest->qop) 581 return CURLE_OUT_OF_MEMORY; 582 } 583 else if(foundAuthInt) { 584 free(digest->qop); 585 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT); 586 if(!digest->qop) 587 return CURLE_OUT_OF_MEMORY; 588 } 589 } 590 else if(strcasecompare(value, "algorithm")) { 591 free(digest->algorithm); 592 digest->algorithm = strdup(content); 593 if(!digest->algorithm) 594 return CURLE_OUT_OF_MEMORY; 595 596 if(strcasecompare(content, "MD5-sess")) 597 digest->algo = ALGO_MD5SESS; 598 else if(strcasecompare(content, "MD5")) 599 digest->algo = ALGO_MD5; 600 else if(strcasecompare(content, "SHA-256")) 601 digest->algo = ALGO_SHA256; 602 else if(strcasecompare(content, "SHA-256-SESS")) 603 digest->algo = ALGO_SHA256SESS; 604 else if(strcasecompare(content, "SHA-512-256")) 605 digest->algo = ALGO_SHA512_256; 606 else if(strcasecompare(content, "SHA-512-256-SESS")) 607 digest->algo = ALGO_SHA512_256SESS; 608 else 609 return CURLE_BAD_CONTENT_ENCODING; 610 } 611 else if(strcasecompare(value, "userhash")) { 612 if(strcasecompare(content, "true")) { 613 digest->userhash = TRUE; 614 } 615 } 616 else { 617 /* Unknown specifier, ignore it! */ 618 } 619 } 620 else 621 break; /* We're done here */ 622 623 /* Pass all additional spaces here */ 624 while(*chlg && ISBLANK(*chlg)) 625 chlg++; 626 627 /* Allow the list to be comma-separated */ 628 if(',' == *chlg) 629 chlg++; 630 } 631 632 /* We had a nonce since before, and we got another one now without 633 'stale=true'. This means we provided bad credentials in the previous 634 request */ 635 if(before && !digest->stale) 636 return CURLE_BAD_CONTENT_ENCODING; 637 638 /* We got this header without a nonce, that's a bad Digest line! */ 639 if(!digest->nonce) 640 return CURLE_BAD_CONTENT_ENCODING; 641 642 /* "<algo>-sess" protocol versions require "auth" or "auth-int" qop */ 643 if(!digest->qop && (digest->algo & SESSION_ALGO)) 644 return CURLE_BAD_CONTENT_ENCODING; 645 646 return CURLE_OK; 647} 648 649/* 650 * auth_create_digest_http_message() 651 * 652 * This is used to generate an HTTP DIGEST response message ready for sending 653 * to the recipient. 654 * 655 * Parameters: 656 * 657 * data [in] - The session handle. 658 * userp [in] - The user name. 659 * passwdp [in] - The user's password. 660 * request [in] - The HTTP request. 661 * uripath [in] - The path of the HTTP uri. 662 * digest [in/out] - The digest data struct being used and modified. 663 * outptr [in/out] - The address where a pointer to newly allocated memory 664 * holding the result will be stored upon completion. 665 * outlen [out] - The length of the output message. 666 * 667 * Returns CURLE_OK on success. 668 */ 669static CURLcode auth_create_digest_http_message( 670 struct Curl_easy *data, 671 const char *userp, 672 const char *passwdp, 673 const unsigned char *request, 674 const unsigned char *uripath, 675 struct digestdata *digest, 676 char **outptr, size_t *outlen, 677 void (*convert_to_ascii)(unsigned char *, unsigned char *), 678 CURLcode (*hash)(unsigned char *, const unsigned char *, 679 const size_t)) 680{ 681 CURLcode result; 682 unsigned char hashbuf[32]; /* 32 bytes/256 bits */ 683 unsigned char request_digest[65]; 684 unsigned char ha1[65]; /* 64 digits and 1 zero byte */ 685 unsigned char ha2[65]; /* 64 digits and 1 zero byte */ 686 char userh[65]; 687 char *cnonce = NULL; 688 size_t cnonce_sz = 0; 689 char *userp_quoted; 690 char *realm_quoted; 691 char *nonce_quoted; 692 char *response = NULL; 693 char *hashthis = NULL; 694 char *tmp = NULL; 695 696 memset(hashbuf, 0, sizeof(hashbuf)); 697 if(!digest->nc) 698 digest->nc = 1; 699 700 if(!digest->cnonce) { 701 char cnoncebuf[33]; 702 result = Curl_rand_hex(data, (unsigned char *)cnoncebuf, 703 sizeof(cnoncebuf)); 704 if(result) 705 return result; 706 707 result = Curl_base64_encode(cnoncebuf, strlen(cnoncebuf), 708 &cnonce, &cnonce_sz); 709 if(result) 710 return result; 711 712 digest->cnonce = cnonce; 713 } 714 715 if(digest->userhash) { 716 hashthis = aprintf("%s:%s", userp, digest->realm ? digest->realm : ""); 717 if(!hashthis) 718 return CURLE_OUT_OF_MEMORY; 719 720 hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); 721 free(hashthis); 722 convert_to_ascii(hashbuf, (unsigned char *)userh); 723 } 724 725 /* 726 If the algorithm is "MD5" or unspecified (which then defaults to MD5): 727 728 A1 = unq(username-value) ":" unq(realm-value) ":" passwd 729 730 If the algorithm is "MD5-sess" then: 731 732 A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":" 733 unq(nonce-value) ":" unq(cnonce-value) 734 */ 735 736 hashthis = aprintf("%s:%s:%s", userp, digest->realm ? digest->realm : "", 737 passwdp); 738 if(!hashthis) 739 return CURLE_OUT_OF_MEMORY; 740 741 hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); 742 free(hashthis); 743 convert_to_ascii(hashbuf, ha1); 744 745 if(digest->algo & SESSION_ALGO) { 746 /* nonce and cnonce are OUTSIDE the hash */ 747 tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce); 748 if(!tmp) 749 return CURLE_OUT_OF_MEMORY; 750 751 hash(hashbuf, (unsigned char *) tmp, strlen(tmp)); 752 free(tmp); 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 && strcasecompare(digest->qop, "auth-int")) { 774 /* We don't support auth-int for PUT or POST */ 775 char hashed[65]; 776 char *hashthis2; 777 778 hash(hashbuf, (const unsigned char *)"", 0); 779 convert_to_ascii(hashbuf, (unsigned char *)hashed); 780 781 hashthis2 = aprintf("%s:%s", hashthis, hashed); 782 free(hashthis); 783 hashthis = hashthis2; 784 } 785 786 if(!hashthis) 787 return CURLE_OUT_OF_MEMORY; 788 789 hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); 790 free(hashthis); 791 convert_to_ascii(hashbuf, ha2); 792 793 if(digest->qop) { 794 hashthis = aprintf("%s:%s:%08x:%s:%s:%s", ha1, digest->nonce, digest->nc, 795 digest->cnonce, digest->qop, ha2); 796 } 797 else { 798 hashthis = aprintf("%s:%s:%s", ha1, digest->nonce, ha2); 799 } 800 801 if(!hashthis) 802 return CURLE_OUT_OF_MEMORY; 803 804 hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); 805 free(hashthis); 806 convert_to_ascii(hashbuf, request_digest); 807 808 /* For test case 64 (snooped from a Mozilla 1.3a request) 809 810 Authorization: Digest username="testuser", realm="testrealm", \ 811 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca" 812 813 Digest parameters are all quoted strings. Username which is provided by 814 the user will need double quotes and backslashes within it escaped. 815 realm, nonce, and opaque will need backslashes as well as they were 816 de-escaped when copied from request header. cnonce is generated with 817 web-safe characters. uri is already percent encoded. nc is 8 hex 818 characters. algorithm and qop with standard values only contain web-safe 819 characters. 820 */ 821 userp_quoted = auth_digest_string_quoted(digest->userhash ? userh : userp); 822 if(!userp_quoted) 823 return CURLE_OUT_OF_MEMORY; 824 if(digest->realm) 825 realm_quoted = auth_digest_string_quoted(digest->realm); 826 else { 827 realm_quoted = malloc(1); 828 if(realm_quoted) 829 realm_quoted[0] = 0; 830 } 831 if(!realm_quoted) { 832 free(userp_quoted); 833 return CURLE_OUT_OF_MEMORY; 834 } 835 nonce_quoted = auth_digest_string_quoted(digest->nonce); 836 if(!nonce_quoted) { 837 free(realm_quoted); 838 free(userp_quoted); 839 return CURLE_OUT_OF_MEMORY; 840 } 841 842 if(digest->qop) { 843 response = aprintf("username=\"%s\", " 844 "realm=\"%s\", " 845 "nonce=\"%s\", " 846 "uri=\"%s\", " 847 "cnonce=\"%s\", " 848 "nc=%08x, " 849 "qop=%s, " 850 "response=\"%s\"", 851 userp_quoted, 852 realm_quoted, 853 nonce_quoted, 854 uripath, 855 digest->cnonce, 856 digest->nc, 857 digest->qop, 858 request_digest); 859 860 /* Increment nonce-count to use another nc value for the next request */ 861 digest->nc++; 862 } 863 else { 864 response = aprintf("username=\"%s\", " 865 "realm=\"%s\", " 866 "nonce=\"%s\", " 867 "uri=\"%s\", " 868 "response=\"%s\"", 869 userp_quoted, 870 realm_quoted, 871 nonce_quoted, 872 uripath, 873 request_digest); 874 } 875 free(nonce_quoted); 876 free(realm_quoted); 877 free(userp_quoted); 878 if(!response) 879 return CURLE_OUT_OF_MEMORY; 880 881 /* Add the optional fields */ 882 if(digest->opaque) { 883 char *opaque_quoted; 884 /* Append the opaque */ 885 opaque_quoted = auth_digest_string_quoted(digest->opaque); 886 if(!opaque_quoted) { 887 free(response); 888 return CURLE_OUT_OF_MEMORY; 889 } 890 tmp = aprintf("%s, opaque=\"%s\"", response, opaque_quoted); 891 free(response); 892 free(opaque_quoted); 893 if(!tmp) 894 return CURLE_OUT_OF_MEMORY; 895 896 response = tmp; 897 } 898 899 if(digest->algorithm) { 900 /* Append the algorithm */ 901 tmp = aprintf("%s, algorithm=%s", response, digest->algorithm); 902 free(response); 903 if(!tmp) 904 return CURLE_OUT_OF_MEMORY; 905 906 response = tmp; 907 } 908 909 if(digest->userhash) { 910 /* Append the userhash */ 911 tmp = aprintf("%s, userhash=true", response); 912 free(response); 913 if(!tmp) 914 return CURLE_OUT_OF_MEMORY; 915 916 response = tmp; 917 } 918 919 /* Return the output */ 920 *outptr = response; 921 *outlen = strlen(response); 922 923 return CURLE_OK; 924} 925 926/* 927 * Curl_auth_create_digest_http_message() 928 * 929 * This is used to generate an HTTP DIGEST response message ready for sending 930 * to the recipient. 931 * 932 * Parameters: 933 * 934 * data [in] - The session handle. 935 * userp [in] - The user name. 936 * passwdp [in] - The user's password. 937 * request [in] - The HTTP request. 938 * uripath [in] - The path of the HTTP uri. 939 * digest [in/out] - The digest data struct being used and modified. 940 * outptr [in/out] - The address where a pointer to newly allocated memory 941 * holding the result will be stored upon completion. 942 * outlen [out] - The length of the output message. 943 * 944 * Returns CURLE_OK on success. 945 */ 946CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, 947 const char *userp, 948 const char *passwdp, 949 const unsigned char *request, 950 const unsigned char *uripath, 951 struct digestdata *digest, 952 char **outptr, size_t *outlen) 953{ 954 if(digest->algo <= ALGO_MD5SESS) 955 return auth_create_digest_http_message(data, userp, passwdp, 956 request, uripath, digest, 957 outptr, outlen, 958 auth_digest_md5_to_ascii, 959 Curl_md5it); 960 DEBUGASSERT(digest->algo <= ALGO_SHA512_256SESS); 961 return auth_create_digest_http_message(data, userp, passwdp, 962 request, uripath, digest, 963 outptr, outlen, 964 auth_digest_sha256_to_ascii, 965 Curl_sha256it); 966} 967 968/* 969 * Curl_auth_digest_cleanup() 970 * 971 * This is used to clean up the digest specific data. 972 * 973 * Parameters: 974 * 975 * digest [in/out] - The digest data struct being cleaned up. 976 * 977 */ 978void Curl_auth_digest_cleanup(struct digestdata *digest) 979{ 980 Curl_safefree(digest->nonce); 981 Curl_safefree(digest->cnonce); 982 Curl_safefree(digest->realm); 983 Curl_safefree(digest->opaque); 984 Curl_safefree(digest->qop); 985 Curl_safefree(digest->algorithm); 986 987 digest->nc = 0; 988 digest->algo = ALGO_MD5; /* default algorithm */ 989 digest->stale = FALSE; /* default means normal, not stale */ 990 digest->userhash = FALSE; 991} 992#endif /* !USE_WINDOWS_SSPI */ 993 994#endif /* !CURL_DISABLE_DIGEST_AUTH */ 995