1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 2014 - 2016, Steve Holme, <steve_holme (at) hotmail.com>. 9 * Copyright (C) 2015 - 2019, Daniel Stenberg, <daniel (at) haxx.se>, et al. 10 * 11 * This software is licensed as described in the file COPYING, which 12 * you should have received as part of this distribution. The terms 13 * are also available at https://curl.haxx.se/docs/copyright.html. 14 * 15 * You may opt to use, copy, modify, merge, publish, distribute and/or sell 16 * copies of the Software, and permit persons to whom the Software is 17 * furnished to do so, under the terms of the COPYING file. 18 * 19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 20 * KIND, either express or implied. 21 * 22 * RFC2831 DIGEST-MD5 authentication 23 * 24 ***************************************************************************/ 25 26 #include "curl_setup.h" 27 28 #if defined(USE_WINDOWS_SSPI) && !defined(CURL_DISABLE_CRYPTO_AUTH) 29 30 #include <curl/curl.h> 31 32 #include "vauth/vauth.h" 33 #include "vauth/digest.h" 34 #include "urldata.h" 35 #include "curl_base64.h" 36 #include "warnless.h" 37 #include "curl_multibyte.h" 38 #include "sendf.h" 39 #include "strdup.h" 40 #include "strcase.h" 41 42 /* The last #include files should be: */ 43 #include "curl_memory.h" 44 #include "memdebug.h" 45 46 /* 47 * Curl_auth_is_digest_supported() 48 * 49 * This is used to evaluate if DIGEST is supported. 50 * 51 * Parameters: None 52 * 53 * Returns TRUE if DIGEST is supported by Windows SSPI. 54 */ 55 bool Curl_auth_is_digest_supported(void) 56 { 57 PSecPkgInfo SecurityPackage; 58 SECURITY_STATUS status; 59 60 /* Query the security package for Digest */ 61 status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), 62 &SecurityPackage); 63 64 return (status == SEC_E_OK ? TRUE : FALSE); 65 } 66 67 /* 68 * Curl_auth_create_digest_md5_message() 69 * 70 * This is used to generate an already encoded DIGEST-MD5 response message 71 * ready for sending to the recipient. 72 * 73 * Parameters: 74 * 75 * data [in] - The session handle. 76 * chlg64 [in] - The base64 encoded challenge message. 77 * userp [in] - The user name in the format User or Domain\User. 78 * passwdp [in] - The user's password. 79 * service [in] - The service type such as http, smtp, pop or imap. 80 * outptr [in/out] - The address where a pointer to newly allocated memory 81 * holding the result will be stored upon completion. 82 * outlen [out] - The length of the output message. 83 * 84 * Returns CURLE_OK on success. 85 */ 86 CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, 87 const char *chlg64, 88 const char *userp, 89 const char *passwdp, 90 const char *service, 91 char **outptr, size_t *outlen) 92 { 93 CURLcode result = CURLE_OK; 94 TCHAR *spn = NULL; 95 size_t chlglen = 0; 96 size_t token_max = 0; 97 unsigned char *input_token = NULL; 98 unsigned char *output_token = NULL; 99 CredHandle credentials; 100 CtxtHandle context; 101 PSecPkgInfo SecurityPackage; 102 SEC_WINNT_AUTH_IDENTITY identity; 103 SEC_WINNT_AUTH_IDENTITY *p_identity; 104 SecBuffer chlg_buf; 105 SecBuffer resp_buf; 106 SecBufferDesc chlg_desc; 107 SecBufferDesc resp_desc; 108 SECURITY_STATUS status; 109 unsigned long attrs; 110 TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ 111 112 /* Decode the base-64 encoded challenge message */ 113 if(strlen(chlg64) && *chlg64 != '=') { 114 result = Curl_base64_decode(chlg64, &input_token, &chlglen); 115 if(result) 116 return result; 117 } 118 119 /* Ensure we have a valid challenge message */ 120 if(!input_token) { 121 infof(data, "DIGEST-MD5 handshake failure (empty challenge message)\n"); 122 123 return CURLE_BAD_CONTENT_ENCODING; 124 } 125 126 /* Query the security package for DigestSSP */ 127 status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), 128 &SecurityPackage); 129 if(status != SEC_E_OK) { 130 free(input_token); 131 132 return CURLE_NOT_BUILT_IN; 133 } 134 135 token_max = SecurityPackage->cbMaxToken; 136 137 /* Release the package buffer as it is not required anymore */ 138 s_pSecFn->FreeContextBuffer(SecurityPackage); 139 140 /* Allocate our response buffer */ 141 output_token = malloc(token_max); 142 if(!output_token) { 143 free(input_token); 144 145 return CURLE_OUT_OF_MEMORY; 146 } 147 148 /* Generate our SPN */ 149 spn = Curl_auth_build_spn(service, data->conn->host.name, NULL); 150 if(!spn) { 151 free(output_token); 152 free(input_token); 153 154 return CURLE_OUT_OF_MEMORY; 155 } 156 157 if(userp && *userp) { 158 /* Populate our identity structure */ 159 result = Curl_create_sspi_identity(userp, passwdp, &identity); 160 if(result) { 161 free(spn); 162 free(output_token); 163 free(input_token); 164 165 return result; 166 } 167 168 /* Allow proper cleanup of the identity structure */ 169 p_identity = &identity; 170 } 171 else 172 /* Use the current Windows user */ 173 p_identity = NULL; 174 175 /* Acquire our credentials handle */ 176 status = s_pSecFn->AcquireCredentialsHandle(NULL, 177 (TCHAR *) TEXT(SP_NAME_DIGEST), 178 SECPKG_CRED_OUTBOUND, NULL, 179 p_identity, NULL, NULL, 180 &credentials, &expiry); 181 182 if(status != SEC_E_OK) { 183 Curl_sspi_free_identity(p_identity); 184 free(spn); 185 free(output_token); 186 free(input_token); 187 188 return CURLE_LOGIN_DENIED; 189 } 190 191 /* Setup the challenge "input" security buffer */ 192 chlg_desc.ulVersion = SECBUFFER_VERSION; 193 chlg_desc.cBuffers = 1; 194 chlg_desc.pBuffers = &chlg_buf; 195 chlg_buf.BufferType = SECBUFFER_TOKEN; 196 chlg_buf.pvBuffer = input_token; 197 chlg_buf.cbBuffer = curlx_uztoul(chlglen); 198 199 /* Setup the response "output" security buffer */ 200 resp_desc.ulVersion = SECBUFFER_VERSION; 201 resp_desc.cBuffers = 1; 202 resp_desc.pBuffers = &resp_buf; 203 resp_buf.BufferType = SECBUFFER_TOKEN; 204 resp_buf.pvBuffer = output_token; 205 resp_buf.cbBuffer = curlx_uztoul(token_max); 206 207 /* Generate our response message */ 208 status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, spn, 209 0, 0, 0, &chlg_desc, 0, 210 &context, &resp_desc, &attrs, 211 &expiry); 212 213 if(status == SEC_I_COMPLETE_NEEDED || 214 status == SEC_I_COMPLETE_AND_CONTINUE) 215 s_pSecFn->CompleteAuthToken(&credentials, &resp_desc); 216 else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { 217 s_pSecFn->FreeCredentialsHandle(&credentials); 218 Curl_sspi_free_identity(p_identity); 219 free(spn); 220 free(output_token); 221 free(input_token); 222 223 return CURLE_RECV_ERROR; 224 } 225 226 /* Base64 encode the response */ 227 result = Curl_base64_encode(data, (char *) output_token, resp_buf.cbBuffer, 228 outptr, outlen); 229 230 /* Free our handles */ 231 s_pSecFn->DeleteSecurityContext(&context); 232 s_pSecFn->FreeCredentialsHandle(&credentials); 233 234 /* Free the identity structure */ 235 Curl_sspi_free_identity(p_identity); 236 237 /* Free the SPN */ 238 free(spn); 239 240 /* Free the response buffer */ 241 free(output_token); 242 243 /* Free the decoded challenge message */ 244 free(input_token); 245 246 return result; 247 } 248 249 /* 250 * Curl_override_sspi_http_realm() 251 * 252 * This is used to populate the domain in a SSPI identity structure 253 * The realm is extracted from the challenge message and used as the 254 * domain if it is not already explicitly set. 255 * 256 * Parameters: 257 * 258 * chlg [in] - The challenge message. 259 * identity [in/out] - The identity structure. 260 * 261 * Returns CURLE_OK on success. 262 */ 263 CURLcode Curl_override_sspi_http_realm(const char *chlg, 264 SEC_WINNT_AUTH_IDENTITY *identity) 265 { 266 xcharp_u domain, dup_domain; 267 268 /* If domain is blank or unset, check challenge message for realm */ 269 if(!identity->Domain || !identity->DomainLength) { 270 for(;;) { 271 char value[DIGEST_MAX_VALUE_LENGTH]; 272 char content[DIGEST_MAX_CONTENT_LENGTH]; 273 274 /* Pass all additional spaces here */ 275 while(*chlg && ISSPACE(*chlg)) 276 chlg++; 277 278 /* Extract a value=content pair */ 279 if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) { 280 if(strcasecompare(value, "realm")) { 281 282 /* Setup identity's domain and length */ 283 domain.tchar_ptr = Curl_convert_UTF8_to_tchar((char *) content); 284 if(!domain.tchar_ptr) 285 return CURLE_OUT_OF_MEMORY; 286 287 dup_domain.tchar_ptr = _tcsdup(domain.tchar_ptr); 288 if(!dup_domain.tchar_ptr) { 289 Curl_unicodefree(domain.tchar_ptr); 290 return CURLE_OUT_OF_MEMORY; 291 } 292 293 free(identity->Domain); 294 identity->Domain = dup_domain.tbyte_ptr; 295 identity->DomainLength = curlx_uztoul(_tcslen(dup_domain.tchar_ptr)); 296 dup_domain.tchar_ptr = NULL; 297 298 Curl_unicodefree(domain.tchar_ptr); 299 } 300 else { 301 /* Unknown specifier, ignore it! */ 302 } 303 } 304 else 305 break; /* We're done here */ 306 307 /* Pass all additional spaces here */ 308 while(*chlg && ISSPACE(*chlg)) 309 chlg++; 310 311 /* Allow the list to be comma-separated */ 312 if(',' == *chlg) 313 chlg++; 314 } 315 } 316 317 return CURLE_OK; 318 } 319 320 /* 321 * Curl_auth_decode_digest_http_message() 322 * 323 * This is used to decode a HTTP DIGEST challenge message into the separate 324 * attributes. 325 * 326 * Parameters: 327 * 328 * chlg [in] - The challenge message. 329 * digest [in/out] - The digest data struct being used and modified. 330 * 331 * Returns CURLE_OK on success. 332 */ 333 CURLcode Curl_auth_decode_digest_http_message(const char *chlg, 334 struct digestdata *digest) 335 { 336 size_t chlglen = strlen(chlg); 337 338 /* We had an input token before so if there's another one now that means we 339 provided bad credentials in the previous request or it's stale. */ 340 if(digest->input_token) { 341 bool stale = false; 342 const char *p = chlg; 343 344 /* Check for the 'stale' directive */ 345 for(;;) { 346 char value[DIGEST_MAX_VALUE_LENGTH]; 347 char content[DIGEST_MAX_CONTENT_LENGTH]; 348 349 while(*p && ISSPACE(*p)) 350 p++; 351 352 if(!Curl_auth_digest_get_pair(p, value, content, &p)) 353 break; 354 355 if(strcasecompare(value, "stale") && 356 strcasecompare(content, "true")) { 357 stale = true; 358 break; 359 } 360 361 while(*p && ISSPACE(*p)) 362 p++; 363 364 if(',' == *p) 365 p++; 366 } 367 368 if(stale) 369 Curl_auth_digest_cleanup(digest); 370 else 371 return CURLE_LOGIN_DENIED; 372 } 373 374 /* Store the challenge for use later */ 375 digest->input_token = (BYTE *) Curl_memdup(chlg, chlglen + 1); 376 if(!digest->input_token) 377 return CURLE_OUT_OF_MEMORY; 378 379 digest->input_token_len = chlglen; 380 381 return CURLE_OK; 382 } 383 384 /* 385 * Curl_auth_create_digest_http_message() 386 * 387 * This is used to generate a HTTP DIGEST response message ready for sending 388 * to the recipient. 389 * 390 * Parameters: 391 * 392 * data [in] - The session handle. 393 * userp [in] - The user name in the format User or Domain\User. 394 * passwdp [in] - The user's password. 395 * request [in] - The HTTP request. 396 * uripath [in] - The path of the HTTP uri. 397 * digest [in/out] - The digest data struct being used and modified. 398 * outptr [in/out] - The address where a pointer to newly allocated memory 399 * holding the result will be stored upon completion. 400 * outlen [out] - The length of the output message. 401 * 402 * Returns CURLE_OK on success. 403 */ 404 CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, 405 const char *userp, 406 const char *passwdp, 407 const unsigned char *request, 408 const unsigned char *uripath, 409 struct digestdata *digest, 410 char **outptr, size_t *outlen) 411 { 412 size_t token_max; 413 char *resp; 414 BYTE *output_token; 415 size_t output_token_len = 0; 416 PSecPkgInfo SecurityPackage; 417 SecBuffer chlg_buf[5]; 418 SecBufferDesc chlg_desc; 419 SECURITY_STATUS status; 420 421 (void) data; 422 423 /* Query the security package for DigestSSP */ 424 status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), 425 &SecurityPackage); 426 if(status != SEC_E_OK) 427 return CURLE_NOT_BUILT_IN; 428 429 token_max = SecurityPackage->cbMaxToken; 430 431 /* Release the package buffer as it is not required anymore */ 432 s_pSecFn->FreeContextBuffer(SecurityPackage); 433 434 /* Allocate the output buffer according to the max token size as indicated 435 by the security package */ 436 output_token = malloc(token_max); 437 if(!output_token) { 438 return CURLE_OUT_OF_MEMORY; 439 } 440 441 /* If the user/passwd that was used to make the identity for http_context 442 has changed then delete that context. */ 443 if((userp && !digest->user) || (!userp && digest->user) || 444 (passwdp && !digest->passwd) || (!passwdp && digest->passwd) || 445 (userp && digest->user && strcmp(userp, digest->user)) || 446 (passwdp && digest->passwd && strcmp(passwdp, digest->passwd))) { 447 if(digest->http_context) { 448 s_pSecFn->DeleteSecurityContext(digest->http_context); 449 Curl_safefree(digest->http_context); 450 } 451 Curl_safefree(digest->user); 452 Curl_safefree(digest->passwd); 453 } 454 455 if(digest->http_context) { 456 chlg_desc.ulVersion = SECBUFFER_VERSION; 457 chlg_desc.cBuffers = 5; 458 chlg_desc.pBuffers = chlg_buf; 459 chlg_buf[0].BufferType = SECBUFFER_TOKEN; 460 chlg_buf[0].pvBuffer = NULL; 461 chlg_buf[0].cbBuffer = 0; 462 chlg_buf[1].BufferType = SECBUFFER_PKG_PARAMS; 463 chlg_buf[1].pvBuffer = (void *) request; 464 chlg_buf[1].cbBuffer = curlx_uztoul(strlen((const char *) request)); 465 chlg_buf[2].BufferType = SECBUFFER_PKG_PARAMS; 466 chlg_buf[2].pvBuffer = (void *) uripath; 467 chlg_buf[2].cbBuffer = curlx_uztoul(strlen((const char *) uripath)); 468 chlg_buf[3].BufferType = SECBUFFER_PKG_PARAMS; 469 chlg_buf[3].pvBuffer = NULL; 470 chlg_buf[3].cbBuffer = 0; 471 chlg_buf[4].BufferType = SECBUFFER_PADDING; 472 chlg_buf[4].pvBuffer = output_token; 473 chlg_buf[4].cbBuffer = curlx_uztoul(token_max); 474 475 status = s_pSecFn->MakeSignature(digest->http_context, 0, &chlg_desc, 0); 476 if(status == SEC_E_OK) 477 output_token_len = chlg_buf[4].cbBuffer; 478 else { /* delete the context so a new one can be made */ 479 infof(data, "digest_sspi: MakeSignature failed, error 0x%08lx\n", 480 (long)status); 481 s_pSecFn->DeleteSecurityContext(digest->http_context); 482 Curl_safefree(digest->http_context); 483 } 484 } 485 486 if(!digest->http_context) { 487 CredHandle credentials; 488 SEC_WINNT_AUTH_IDENTITY identity; 489 SEC_WINNT_AUTH_IDENTITY *p_identity; 490 SecBuffer resp_buf; 491 SecBufferDesc resp_desc; 492 unsigned long attrs; 493 TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ 494 TCHAR *spn; 495 496 /* free the copy of user/passwd used to make the previous identity */ 497 Curl_safefree(digest->user); 498 Curl_safefree(digest->passwd); 499 500 if(userp && *userp) { 501 /* Populate our identity structure */ 502 if(Curl_create_sspi_identity(userp, passwdp, &identity)) { 503 free(output_token); 504 return CURLE_OUT_OF_MEMORY; 505 } 506 507 /* Populate our identity domain */ 508 if(Curl_override_sspi_http_realm((const char *) digest->input_token, 509 &identity)) { 510 free(output_token); 511 return CURLE_OUT_OF_MEMORY; 512 } 513 514 /* Allow proper cleanup of the identity structure */ 515 p_identity = &identity; 516 } 517 else 518 /* Use the current Windows user */ 519 p_identity = NULL; 520 521 if(userp) { 522 digest->user = strdup(userp); 523 524 if(!digest->user) { 525 free(output_token); 526 return CURLE_OUT_OF_MEMORY; 527 } 528 } 529 530 if(passwdp) { 531 digest->passwd = strdup(passwdp); 532 533 if(!digest->passwd) { 534 free(output_token); 535 Curl_safefree(digest->user); 536 return CURLE_OUT_OF_MEMORY; 537 } 538 } 539 540 /* Acquire our credentials handle */ 541 status = s_pSecFn->AcquireCredentialsHandle(NULL, 542 (TCHAR *) TEXT(SP_NAME_DIGEST), 543 SECPKG_CRED_OUTBOUND, NULL, 544 p_identity, NULL, NULL, 545 &credentials, &expiry); 546 if(status != SEC_E_OK) { 547 Curl_sspi_free_identity(p_identity); 548 free(output_token); 549 550 return CURLE_LOGIN_DENIED; 551 } 552 553 /* Setup the challenge "input" security buffer if present */ 554 chlg_desc.ulVersion = SECBUFFER_VERSION; 555 chlg_desc.cBuffers = 3; 556 chlg_desc.pBuffers = chlg_buf; 557 chlg_buf[0].BufferType = SECBUFFER_TOKEN; 558 chlg_buf[0].pvBuffer = digest->input_token; 559 chlg_buf[0].cbBuffer = curlx_uztoul(digest->input_token_len); 560 chlg_buf[1].BufferType = SECBUFFER_PKG_PARAMS; 561 chlg_buf[1].pvBuffer = (void *) request; 562 chlg_buf[1].cbBuffer = curlx_uztoul(strlen((const char *) request)); 563 chlg_buf[2].BufferType = SECBUFFER_PKG_PARAMS; 564 chlg_buf[2].pvBuffer = NULL; 565 chlg_buf[2].cbBuffer = 0; 566 567 /* Setup the response "output" security buffer */ 568 resp_desc.ulVersion = SECBUFFER_VERSION; 569 resp_desc.cBuffers = 1; 570 resp_desc.pBuffers = &resp_buf; 571 resp_buf.BufferType = SECBUFFER_TOKEN; 572 resp_buf.pvBuffer = output_token; 573 resp_buf.cbBuffer = curlx_uztoul(token_max); 574 575 spn = Curl_convert_UTF8_to_tchar((char *) uripath); 576 if(!spn) { 577 s_pSecFn->FreeCredentialsHandle(&credentials); 578 579 Curl_sspi_free_identity(p_identity); 580 free(output_token); 581 582 return CURLE_OUT_OF_MEMORY; 583 } 584 585 /* Allocate our new context handle */ 586 digest->http_context = calloc(1, sizeof(CtxtHandle)); 587 if(!digest->http_context) 588 return CURLE_OUT_OF_MEMORY; 589 590 /* Generate our response message */ 591 status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, 592 spn, 593 ISC_REQ_USE_HTTP_STYLE, 0, 0, 594 &chlg_desc, 0, 595 digest->http_context, 596 &resp_desc, &attrs, &expiry); 597 Curl_unicodefree(spn); 598 599 if(status == SEC_I_COMPLETE_NEEDED || 600 status == SEC_I_COMPLETE_AND_CONTINUE) 601 s_pSecFn->CompleteAuthToken(&credentials, &resp_desc); 602 else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { 603 s_pSecFn->FreeCredentialsHandle(&credentials); 604 605 Curl_sspi_free_identity(p_identity); 606 free(output_token); 607 608 Curl_safefree(digest->http_context); 609 610 return CURLE_OUT_OF_MEMORY; 611 } 612 613 output_token_len = resp_buf.cbBuffer; 614 615 s_pSecFn->FreeCredentialsHandle(&credentials); 616 Curl_sspi_free_identity(p_identity); 617 } 618 619 resp = malloc(output_token_len + 1); 620 if(!resp) { 621 free(output_token); 622 623 return CURLE_OUT_OF_MEMORY; 624 } 625 626 /* Copy the generated response */ 627 memcpy(resp, output_token, output_token_len); 628 resp[output_token_len] = 0; 629 630 /* Return the response */ 631 *outptr = resp; 632 *outlen = output_token_len; 633 634 /* Free the response buffer */ 635 free(output_token); 636 637 return CURLE_OK; 638 } 639 640 /* 641 * Curl_auth_digest_cleanup() 642 * 643 * This is used to clean up the digest specific data. 644 * 645 * Parameters: 646 * 647 * digest [in/out] - The digest data struct being cleaned up. 648 * 649 */ 650 void Curl_auth_digest_cleanup(struct digestdata *digest) 651 { 652 /* Free the input token */ 653 Curl_safefree(digest->input_token); 654 655 /* Reset any variables */ 656 digest->input_token_len = 0; 657 658 /* Delete security context */ 659 if(digest->http_context) { 660 s_pSecFn->DeleteSecurityContext(digest->http_context); 661 Curl_safefree(digest->http_context); 662 } 663 664 /* Free the copy of user/passwd used to make the identity for http_context */ 665 Curl_safefree(digest->user); 666 Curl_safefree(digest->passwd); 667 } 668 669 #endif /* USE_WINDOWS_SSPI && !CURL_DISABLE_CRYPTO_AUTH */ 670