1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 2014 - 2016, Steve Holme, <steve_holme@hotmail.com>.
9 * Copyright (C) 2015 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
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.se/docs/copyright.html.
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.
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
22 * RFC2831 DIGEST-MD5 authentication
24 ***************************************************************************/
26 #include "curl_setup.h"
28 #if defined(USE_WINDOWS_SSPI) && !defined(CURL_DISABLE_CRYPTO_AUTH)
30 #include <curl/curl.h>
32 #include "vauth/vauth.h"
33 #include "vauth/digest.h"
36 #include "curl_multibyte.h"
42 /* The last #include files should be: */
43 #include "curl_memory.h"
47 * Curl_auth_is_digest_supported()
49 * This is used to evaluate if DIGEST is supported.
53 * Returns TRUE if DIGEST is supported by Windows SSPI.
55 bool Curl_auth_is_digest_supported(void)
57 PSecPkgInfo SecurityPackage;
58 SECURITY_STATUS status;
60 /* Query the security package for Digest */
61 status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST),
64 /* Release the package buffer as it is not required anymore */
65 if(status == SEC_E_OK) {
66 s_pSecFn->FreeContextBuffer(SecurityPackage);
69 return (status == SEC_E_OK ? TRUE : FALSE);
73 * Curl_auth_create_digest_md5_message()
75 * This is used to generate an already encoded DIGEST-MD5 response message
76 * ready for sending to the recipient.
80 * data [in] - The session handle.
81 * chlg [in] - The challenge message.
82 * userp [in] - The user name in the format User or Domain\User.
83 * passwdp [in] - The user's password.
84 * service [in] - The service type such as http, smtp, pop or imap.
85 * out [out] - The result storage.
87 * Returns CURLE_OK on success.
89 CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data,
90 const struct bufref *chlg,
96 CURLcode result = CURLE_OK;
99 unsigned char *output_token = NULL;
100 CredHandle credentials;
102 PSecPkgInfo SecurityPackage;
103 SEC_WINNT_AUTH_IDENTITY identity;
104 SEC_WINNT_AUTH_IDENTITY *p_identity;
107 SecBufferDesc chlg_desc;
108 SecBufferDesc resp_desc;
109 SECURITY_STATUS status;
111 TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */
113 /* Ensure we have a valid challenge message */
114 if(!Curl_bufref_len(chlg)) {
115 infof(data, "DIGEST-MD5 handshake failure (empty challenge message)");
116 return CURLE_BAD_CONTENT_ENCODING;
119 /* Query the security package for DigestSSP */
120 status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST),
122 if(status != SEC_E_OK) {
123 failf(data, "SSPI: couldn't get auth info");
124 return CURLE_AUTH_ERROR;
127 token_max = SecurityPackage->cbMaxToken;
129 /* Release the package buffer as it is not required anymore */
130 s_pSecFn->FreeContextBuffer(SecurityPackage);
132 /* Allocate our response buffer */
133 output_token = malloc(token_max);
135 return CURLE_OUT_OF_MEMORY;
137 /* Generate our SPN */
138 spn = Curl_auth_build_spn(service, data->conn->host.name, NULL);
141 return CURLE_OUT_OF_MEMORY;
144 if(userp && *userp) {
145 /* Populate our identity structure */
146 result = Curl_create_sspi_identity(userp, passwdp, &identity);
153 /* Allow proper cleanup of the identity structure */
154 p_identity = &identity;
157 /* Use the current Windows user */
160 /* Acquire our credentials handle */
161 status = s_pSecFn->AcquireCredentialsHandle(NULL,
162 (TCHAR *) TEXT(SP_NAME_DIGEST),
163 SECPKG_CRED_OUTBOUND, NULL,
164 p_identity, NULL, NULL,
165 &credentials, &expiry);
167 if(status != SEC_E_OK) {
168 Curl_sspi_free_identity(p_identity);
171 return CURLE_LOGIN_DENIED;
174 /* Setup the challenge "input" security buffer */
175 chlg_desc.ulVersion = SECBUFFER_VERSION;
176 chlg_desc.cBuffers = 1;
177 chlg_desc.pBuffers = &chlg_buf;
178 chlg_buf.BufferType = SECBUFFER_TOKEN;
179 chlg_buf.pvBuffer = (void *) Curl_bufref_ptr(chlg);
180 chlg_buf.cbBuffer = curlx_uztoul(Curl_bufref_len(chlg));
182 /* Setup the response "output" security buffer */
183 resp_desc.ulVersion = SECBUFFER_VERSION;
184 resp_desc.cBuffers = 1;
185 resp_desc.pBuffers = &resp_buf;
186 resp_buf.BufferType = SECBUFFER_TOKEN;
187 resp_buf.pvBuffer = output_token;
188 resp_buf.cbBuffer = curlx_uztoul(token_max);
190 /* Generate our response message */
191 status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, spn,
192 0, 0, 0, &chlg_desc, 0,
193 &context, &resp_desc, &attrs,
196 if(status == SEC_I_COMPLETE_NEEDED ||
197 status == SEC_I_COMPLETE_AND_CONTINUE)
198 s_pSecFn->CompleteAuthToken(&credentials, &resp_desc);
199 else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) {
200 #if !defined(CURL_DISABLE_VERBOSE_STRINGS)
201 char buffer[STRERROR_LEN];
204 s_pSecFn->FreeCredentialsHandle(&credentials);
205 Curl_sspi_free_identity(p_identity);
209 if(status == SEC_E_INSUFFICIENT_MEMORY)
210 return CURLE_OUT_OF_MEMORY;
212 infof(data, "schannel: InitializeSecurityContext failed: %s",
213 Curl_sspi_strerror(status, buffer, sizeof(buffer)));
215 return CURLE_AUTH_ERROR;
218 /* Return the response. */
219 Curl_bufref_set(out, output_token, resp_buf.cbBuffer, curl_free);
221 /* Free our handles */
222 s_pSecFn->DeleteSecurityContext(&context);
223 s_pSecFn->FreeCredentialsHandle(&credentials);
225 /* Free the identity structure */
226 Curl_sspi_free_identity(p_identity);
235 * Curl_override_sspi_http_realm()
237 * This is used to populate the domain in a SSPI identity structure
238 * The realm is extracted from the challenge message and used as the
239 * domain if it is not already explicitly set.
243 * chlg [in] - The challenge message.
244 * identity [in/out] - The identity structure.
246 * Returns CURLE_OK on success.
248 CURLcode Curl_override_sspi_http_realm(const char *chlg,
249 SEC_WINNT_AUTH_IDENTITY *identity)
251 xcharp_u domain, dup_domain;
253 /* If domain is blank or unset, check challenge message for realm */
254 if(!identity->Domain || !identity->DomainLength) {
256 char value[DIGEST_MAX_VALUE_LENGTH];
257 char content[DIGEST_MAX_CONTENT_LENGTH];
259 /* Pass all additional spaces here */
260 while(*chlg && ISSPACE(*chlg))
263 /* Extract a value=content pair */
264 if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) {
265 if(strcasecompare(value, "realm")) {
267 /* Setup identity's domain and length */
268 domain.tchar_ptr = curlx_convert_UTF8_to_tchar((char *) content);
269 if(!domain.tchar_ptr)
270 return CURLE_OUT_OF_MEMORY;
272 dup_domain.tchar_ptr = _tcsdup(domain.tchar_ptr);
273 if(!dup_domain.tchar_ptr) {
274 curlx_unicodefree(domain.tchar_ptr);
275 return CURLE_OUT_OF_MEMORY;
278 free(identity->Domain);
279 identity->Domain = dup_domain.tbyte_ptr;
280 identity->DomainLength = curlx_uztoul(_tcslen(dup_domain.tchar_ptr));
281 dup_domain.tchar_ptr = NULL;
283 curlx_unicodefree(domain.tchar_ptr);
286 /* Unknown specifier, ignore it! */
290 break; /* We're done here */
292 /* Pass all additional spaces here */
293 while(*chlg && ISSPACE(*chlg))
296 /* Allow the list to be comma-separated */
306 * Curl_auth_decode_digest_http_message()
308 * This is used to decode a HTTP DIGEST challenge message into the separate
313 * chlg [in] - The challenge message.
314 * digest [in/out] - The digest data struct being used and modified.
316 * Returns CURLE_OK on success.
318 CURLcode Curl_auth_decode_digest_http_message(const char *chlg,
319 struct digestdata *digest)
321 size_t chlglen = strlen(chlg);
323 /* We had an input token before so if there's another one now that means we
324 provided bad credentials in the previous request or it's stale. */
325 if(digest->input_token) {
327 const char *p = chlg;
329 /* Check for the 'stale' directive */
331 char value[DIGEST_MAX_VALUE_LENGTH];
332 char content[DIGEST_MAX_CONTENT_LENGTH];
334 while(*p && ISSPACE(*p))
337 if(!Curl_auth_digest_get_pair(p, value, content, &p))
340 if(strcasecompare(value, "stale") &&
341 strcasecompare(content, "true")) {
346 while(*p && ISSPACE(*p))
354 Curl_auth_digest_cleanup(digest);
356 return CURLE_LOGIN_DENIED;
359 /* Store the challenge for use later */
360 digest->input_token = (BYTE *) Curl_memdup(chlg, chlglen + 1);
361 if(!digest->input_token)
362 return CURLE_OUT_OF_MEMORY;
364 digest->input_token_len = chlglen;
370 * Curl_auth_create_digest_http_message()
372 * This is used to generate a HTTP DIGEST response message ready for sending
377 * data [in] - The session handle.
378 * userp [in] - The user name in the format User or Domain\User.
379 * passwdp [in] - The user's password.
380 * request [in] - The HTTP request.
381 * uripath [in] - The path of the HTTP uri.
382 * digest [in/out] - The digest data struct being used and modified.
383 * outptr [in/out] - The address where a pointer to newly allocated memory
384 * holding the result will be stored upon completion.
385 * outlen [out] - The length of the output message.
387 * Returns CURLE_OK on success.
389 CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
392 const unsigned char *request,
393 const unsigned char *uripath,
394 struct digestdata *digest,
395 char **outptr, size_t *outlen)
400 size_t output_token_len = 0;
401 PSecPkgInfo SecurityPackage;
402 SecBuffer chlg_buf[5];
403 SecBufferDesc chlg_desc;
404 SECURITY_STATUS status;
408 /* Query the security package for DigestSSP */
409 status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST),
411 if(status != SEC_E_OK) {
412 failf(data, "SSPI: couldn't get auth info");
413 return CURLE_AUTH_ERROR;
416 token_max = SecurityPackage->cbMaxToken;
418 /* Release the package buffer as it is not required anymore */
419 s_pSecFn->FreeContextBuffer(SecurityPackage);
421 /* Allocate the output buffer according to the max token size as indicated
422 by the security package */
423 output_token = malloc(token_max);
425 return CURLE_OUT_OF_MEMORY;
428 /* If the user/passwd that was used to make the identity for http_context
429 has changed then delete that context. */
430 if((userp && !digest->user) || (!userp && digest->user) ||
431 (passwdp && !digest->passwd) || (!passwdp && digest->passwd) ||
432 (userp && digest->user && strcmp(userp, digest->user)) ||
433 (passwdp && digest->passwd && strcmp(passwdp, digest->passwd))) {
434 if(digest->http_context) {
435 s_pSecFn->DeleteSecurityContext(digest->http_context);
436 Curl_safefree(digest->http_context);
438 Curl_safefree(digest->user);
439 Curl_safefree(digest->passwd);
442 if(digest->http_context) {
443 chlg_desc.ulVersion = SECBUFFER_VERSION;
444 chlg_desc.cBuffers = 5;
445 chlg_desc.pBuffers = chlg_buf;
446 chlg_buf[0].BufferType = SECBUFFER_TOKEN;
447 chlg_buf[0].pvBuffer = NULL;
448 chlg_buf[0].cbBuffer = 0;
449 chlg_buf[1].BufferType = SECBUFFER_PKG_PARAMS;
450 chlg_buf[1].pvBuffer = (void *) request;
451 chlg_buf[1].cbBuffer = curlx_uztoul(strlen((const char *) request));
452 chlg_buf[2].BufferType = SECBUFFER_PKG_PARAMS;
453 chlg_buf[2].pvBuffer = (void *) uripath;
454 chlg_buf[2].cbBuffer = curlx_uztoul(strlen((const char *) uripath));
455 chlg_buf[3].BufferType = SECBUFFER_PKG_PARAMS;
456 chlg_buf[3].pvBuffer = NULL;
457 chlg_buf[3].cbBuffer = 0;
458 chlg_buf[4].BufferType = SECBUFFER_PADDING;
459 chlg_buf[4].pvBuffer = output_token;
460 chlg_buf[4].cbBuffer = curlx_uztoul(token_max);
462 status = s_pSecFn->MakeSignature(digest->http_context, 0, &chlg_desc, 0);
463 if(status == SEC_E_OK)
464 output_token_len = chlg_buf[4].cbBuffer;
465 else { /* delete the context so a new one can be made */
466 infof(data, "digest_sspi: MakeSignature failed, error 0x%08lx",
468 s_pSecFn->DeleteSecurityContext(digest->http_context);
469 Curl_safefree(digest->http_context);
473 if(!digest->http_context) {
474 CredHandle credentials;
475 SEC_WINNT_AUTH_IDENTITY identity;
476 SEC_WINNT_AUTH_IDENTITY *p_identity;
478 SecBufferDesc resp_desc;
480 TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */
483 /* free the copy of user/passwd used to make the previous identity */
484 Curl_safefree(digest->user);
485 Curl_safefree(digest->passwd);
487 if(userp && *userp) {
488 /* Populate our identity structure */
489 if(Curl_create_sspi_identity(userp, passwdp, &identity)) {
491 return CURLE_OUT_OF_MEMORY;
494 /* Populate our identity domain */
495 if(Curl_override_sspi_http_realm((const char *) digest->input_token,
498 return CURLE_OUT_OF_MEMORY;
501 /* Allow proper cleanup of the identity structure */
502 p_identity = &identity;
505 /* Use the current Windows user */
509 digest->user = strdup(userp);
513 return CURLE_OUT_OF_MEMORY;
518 digest->passwd = strdup(passwdp);
520 if(!digest->passwd) {
522 Curl_safefree(digest->user);
523 return CURLE_OUT_OF_MEMORY;
527 /* Acquire our credentials handle */
528 status = s_pSecFn->AcquireCredentialsHandle(NULL,
529 (TCHAR *) TEXT(SP_NAME_DIGEST),
530 SECPKG_CRED_OUTBOUND, NULL,
531 p_identity, NULL, NULL,
532 &credentials, &expiry);
533 if(status != SEC_E_OK) {
534 Curl_sspi_free_identity(p_identity);
537 return CURLE_LOGIN_DENIED;
540 /* Setup the challenge "input" security buffer if present */
541 chlg_desc.ulVersion = SECBUFFER_VERSION;
542 chlg_desc.cBuffers = 3;
543 chlg_desc.pBuffers = chlg_buf;
544 chlg_buf[0].BufferType = SECBUFFER_TOKEN;
545 chlg_buf[0].pvBuffer = digest->input_token;
546 chlg_buf[0].cbBuffer = curlx_uztoul(digest->input_token_len);
547 chlg_buf[1].BufferType = SECBUFFER_PKG_PARAMS;
548 chlg_buf[1].pvBuffer = (void *) request;
549 chlg_buf[1].cbBuffer = curlx_uztoul(strlen((const char *) request));
550 chlg_buf[2].BufferType = SECBUFFER_PKG_PARAMS;
551 chlg_buf[2].pvBuffer = NULL;
552 chlg_buf[2].cbBuffer = 0;
554 /* Setup the response "output" security buffer */
555 resp_desc.ulVersion = SECBUFFER_VERSION;
556 resp_desc.cBuffers = 1;
557 resp_desc.pBuffers = &resp_buf;
558 resp_buf.BufferType = SECBUFFER_TOKEN;
559 resp_buf.pvBuffer = output_token;
560 resp_buf.cbBuffer = curlx_uztoul(token_max);
562 spn = curlx_convert_UTF8_to_tchar((char *) uripath);
564 s_pSecFn->FreeCredentialsHandle(&credentials);
566 Curl_sspi_free_identity(p_identity);
569 return CURLE_OUT_OF_MEMORY;
572 /* Allocate our new context handle */
573 digest->http_context = calloc(1, sizeof(CtxtHandle));
574 if(!digest->http_context)
575 return CURLE_OUT_OF_MEMORY;
577 /* Generate our response message */
578 status = s_pSecFn->InitializeSecurityContext(&credentials, NULL,
580 ISC_REQ_USE_HTTP_STYLE, 0, 0,
582 digest->http_context,
583 &resp_desc, &attrs, &expiry);
584 curlx_unicodefree(spn);
586 if(status == SEC_I_COMPLETE_NEEDED ||
587 status == SEC_I_COMPLETE_AND_CONTINUE)
588 s_pSecFn->CompleteAuthToken(&credentials, &resp_desc);
589 else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) {
590 #if !defined(CURL_DISABLE_VERBOSE_STRINGS)
591 char buffer[STRERROR_LEN];
594 s_pSecFn->FreeCredentialsHandle(&credentials);
596 Curl_sspi_free_identity(p_identity);
599 Curl_safefree(digest->http_context);
601 if(status == SEC_E_INSUFFICIENT_MEMORY)
602 return CURLE_OUT_OF_MEMORY;
604 infof(data, "schannel: InitializeSecurityContext failed: %s",
605 Curl_sspi_strerror(status, buffer, sizeof(buffer)));
607 return CURLE_AUTH_ERROR;
610 output_token_len = resp_buf.cbBuffer;
612 s_pSecFn->FreeCredentialsHandle(&credentials);
613 Curl_sspi_free_identity(p_identity);
616 resp = malloc(output_token_len + 1);
620 return CURLE_OUT_OF_MEMORY;
623 /* Copy the generated response */
624 memcpy(resp, output_token, output_token_len);
625 resp[output_token_len] = 0;
627 /* Return the response */
629 *outlen = output_token_len;
631 /* Free the response buffer */
638 * Curl_auth_digest_cleanup()
640 * This is used to clean up the digest specific data.
644 * digest [in/out] - The digest data struct being cleaned up.
647 void Curl_auth_digest_cleanup(struct digestdata *digest)
649 /* Free the input token */
650 Curl_safefree(digest->input_token);
652 /* Reset any variables */
653 digest->input_token_len = 0;
655 /* Delete security context */
656 if(digest->http_context) {
657 s_pSecFn->DeleteSecurityContext(digest->http_context);
658 Curl_safefree(digest->http_context);
661 /* Free the copy of user/passwd used to make the identity for http_context */
662 Curl_safefree(digest->user);
663 Curl_safefree(digest->passwd);
666 #endif /* USE_WINDOWS_SSPI && !CURL_DISABLE_CRYPTO_AUTH */