1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 2014 - 2016, Steve Holme, <steve_holme@hotmail.com>.
9 * Copyright (C) 2015 - 2021, Daniel Stenberg, <daniel@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.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 "warnless.h"
36#include "curl_multibyte.h"
37#include "sendf.h"
38#include "strdup.h"
39#include "strcase.h"
40#include "strerror.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*/
55bool 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 /* Release the package buffer as it is not required anymore */
65 if(status == SEC_E_OK) {
66 s_pSecFn->FreeContextBuffer(SecurityPackage);
67 }
68
69 return (status == SEC_E_OK ? TRUE : FALSE);
70}
71
72/*
73 * Curl_auth_create_digest_md5_message()
74 *
75 * This is used to generate an already encoded DIGEST-MD5 response message
76 * ready for sending to the recipient.
77 *
78 * Parameters:
79 *
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.
86 *
87 * Returns CURLE_OK on success.
88 */
89CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data,
90 const struct bufref *chlg,
91 const char *userp,
92 const char *passwdp,
93 const char *service,
94 struct bufref *out)
95{
96 CURLcode result = CURLE_OK;
97 TCHAR *spn = NULL;
98 size_t token_max = 0;
99 unsigned char *output_token = NULL;
100 CredHandle credentials;
101 CtxtHandle context;
102 PSecPkgInfo SecurityPackage;
103 SEC_WINNT_AUTH_IDENTITY identity;
104 SEC_WINNT_AUTH_IDENTITY *p_identity;
105 SecBuffer chlg_buf;
106 SecBuffer resp_buf;
107 SecBufferDesc chlg_desc;
108 SecBufferDesc resp_desc;
109 SECURITY_STATUS status;
110 unsigned long attrs;
111 TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */
112
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;
117 }
118
119 /* Query the security package for DigestSSP */
120 status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST),
121 &SecurityPackage);
122 if(status != SEC_E_OK) {
123 failf(data, "SSPI: couldn't get auth info");
124 return CURLE_AUTH_ERROR;
125 }
126
127 token_max = SecurityPackage->cbMaxToken;
128
129 /* Release the package buffer as it is not required anymore */
130 s_pSecFn->FreeContextBuffer(SecurityPackage);
131
132 /* Allocate our response buffer */
133 output_token = malloc(token_max);
134 if(!output_token)
135 return CURLE_OUT_OF_MEMORY;
136
137 /* Generate our SPN */
138 spn = Curl_auth_build_spn(service, data->conn->host.name, NULL);
139 if(!spn) {
140 free(output_token);
141 return CURLE_OUT_OF_MEMORY;
142 }
143
144 if(userp && *userp) {
145 /* Populate our identity structure */
146 result = Curl_create_sspi_identity(userp, passwdp, &identity);
147 if(result) {
148 free(spn);
149 free(output_token);
150 return result;
151 }
152
153 /* Allow proper cleanup of the identity structure */
154 p_identity = &identity;
155 }
156 else
157 /* Use the current Windows user */
158 p_identity = NULL;
159
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);
166
167 if(status != SEC_E_OK) {
168 Curl_sspi_free_identity(p_identity);
169 free(spn);
170 free(output_token);
171 return CURLE_LOGIN_DENIED;
172 }
173
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));
181
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);
189
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,
194 &expiry);
195
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];
202#endif
203
204 s_pSecFn->FreeCredentialsHandle(&credentials);
205 Curl_sspi_free_identity(p_identity);
206 free(spn);
207 free(output_token);
208
209 if(status == SEC_E_INSUFFICIENT_MEMORY)
210 return CURLE_OUT_OF_MEMORY;
211
212 infof(data, "schannel: InitializeSecurityContext failed: %s",
213 Curl_sspi_strerror(status, buffer, sizeof(buffer)));
214
215 return CURLE_AUTH_ERROR;
216 }
217
218 /* Return the response. */
219 Curl_bufref_set(out, output_token, resp_buf.cbBuffer, curl_free);
220
221 /* Free our handles */
222 s_pSecFn->DeleteSecurityContext(&context);
223 s_pSecFn->FreeCredentialsHandle(&credentials);
224
225 /* Free the identity structure */
226 Curl_sspi_free_identity(p_identity);
227
228 /* Free the SPN */
229 free(spn);
230
231 return result;
232}
233
234/*
235 * Curl_override_sspi_http_realm()
236 *
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.
240 *
241 * Parameters:
242 *
243 * chlg [in] - The challenge message.
244 * identity [in/out] - The identity structure.
245 *
246 * Returns CURLE_OK on success.
247 */
248CURLcode Curl_override_sspi_http_realm(const char *chlg,
249 SEC_WINNT_AUTH_IDENTITY *identity)
250{
251 xcharp_u domain, dup_domain;
252
253 /* If domain is blank or unset, check challenge message for realm */
254 if(!identity->Domain || !identity->DomainLength) {
255 for(;;) {
256 char value[DIGEST_MAX_VALUE_LENGTH];
257 char content[DIGEST_MAX_CONTENT_LENGTH];
258
259 /* Pass all additional spaces here */
260 while(*chlg && ISSPACE(*chlg))
261 chlg++;
262
263 /* Extract a value=content pair */
264 if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) {
265 if(strcasecompare(value, "realm")) {
266
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;
271
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;
276 }
277
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;
282
283 curlx_unicodefree(domain.tchar_ptr);
284 }
285 else {
286 /* Unknown specifier, ignore it! */
287 }
288 }
289 else
290 break; /* We're done here */
291
292 /* Pass all additional spaces here */
293 while(*chlg && ISSPACE(*chlg))
294 chlg++;
295
296 /* Allow the list to be comma-separated */
297 if(',' == *chlg)
298 chlg++;
299 }
300 }
301
302 return CURLE_OK;
303}
304
305/*
306 * Curl_auth_decode_digest_http_message()
307 *
308 * This is used to decode a HTTP DIGEST challenge message into the separate
309 * attributes.
310 *
311 * Parameters:
312 *
313 * chlg [in] - The challenge message.
314 * digest [in/out] - The digest data struct being used and modified.
315 *
316 * Returns CURLE_OK on success.
317 */
318CURLcode Curl_auth_decode_digest_http_message(const char *chlg,
319 struct digestdata *digest)
320{
321 size_t chlglen = strlen(chlg);
322
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) {
326 bool stale = false;
327 const char *p = chlg;
328
329 /* Check for the 'stale' directive */
330 for(;;) {
331 char value[DIGEST_MAX_VALUE_LENGTH];
332 char content[DIGEST_MAX_CONTENT_LENGTH];
333
334 while(*p && ISSPACE(*p))
335 p++;
336
337 if(!Curl_auth_digest_get_pair(p, value, content, &p))
338 break;
339
340 if(strcasecompare(value, "stale") &&
341 strcasecompare(content, "true")) {
342 stale = true;
343 break;
344 }
345
346 while(*p && ISSPACE(*p))
347 p++;
348
349 if(',' == *p)
350 p++;
351 }
352
353 if(stale)
354 Curl_auth_digest_cleanup(digest);
355 else
356 return CURLE_LOGIN_DENIED;
357 }
358
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;
363
364 digest->input_token_len = chlglen;
365
366 return CURLE_OK;
367}
368
369/*
370 * Curl_auth_create_digest_http_message()
371 *
372 * This is used to generate a HTTP DIGEST response message ready for sending
373 * to the recipient.
374 *
375 * Parameters:
376 *
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.
386 *
387 * Returns CURLE_OK on success.
388 */
389CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
390 const char *userp,
391 const char *passwdp,
392 const unsigned char *request,
393 const unsigned char *uripath,
394 struct digestdata *digest,
395 char **outptr, size_t *outlen)
396{
397 size_t token_max;
398 char *resp;
399 BYTE *output_token;
400 size_t output_token_len = 0;
401 PSecPkgInfo SecurityPackage;
402 SecBuffer chlg_buf[5];
403 SecBufferDesc chlg_desc;
404 SECURITY_STATUS status;
405
406 (void) data;
407
408 /* Query the security package for DigestSSP */
409 status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST),
410 &SecurityPackage);
411 if(status != SEC_E_OK) {
412 failf(data, "SSPI: couldn't get auth info");
413 return CURLE_AUTH_ERROR;
414 }
415
416 token_max = SecurityPackage->cbMaxToken;
417
418 /* Release the package buffer as it is not required anymore */
419 s_pSecFn->FreeContextBuffer(SecurityPackage);
420
421 /* Allocate the output buffer according to the max token size as indicated
422 by the security package */
423 output_token = malloc(token_max);
424 if(!output_token) {
425 return CURLE_OUT_OF_MEMORY;
426 }
427
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);
437 }
438 Curl_safefree(digest->user);
439 Curl_safefree(digest->passwd);
440 }
441
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);
461
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",
467 (long)status);
468 s_pSecFn->DeleteSecurityContext(digest->http_context);
469 Curl_safefree(digest->http_context);
470 }
471 }
472
473 if(!digest->http_context) {
474 CredHandle credentials;
475 SEC_WINNT_AUTH_IDENTITY identity;
476 SEC_WINNT_AUTH_IDENTITY *p_identity;
477 SecBuffer resp_buf;
478 SecBufferDesc resp_desc;
479 unsigned long attrs;
480 TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */
481 TCHAR *spn;
482
483 /* free the copy of user/passwd used to make the previous identity */
484 Curl_safefree(digest->user);
485 Curl_safefree(digest->passwd);
486
487 if(userp && *userp) {
488 /* Populate our identity structure */
489 if(Curl_create_sspi_identity(userp, passwdp, &identity)) {
490 free(output_token);
491 return CURLE_OUT_OF_MEMORY;
492 }
493
494 /* Populate our identity domain */
495 if(Curl_override_sspi_http_realm((const char *) digest->input_token,
496 &identity)) {
497 free(output_token);
498 return CURLE_OUT_OF_MEMORY;
499 }
500
501 /* Allow proper cleanup of the identity structure */
502 p_identity = &identity;
503 }
504 else
505 /* Use the current Windows user */
506 p_identity = NULL;
507
508 if(userp) {
509 digest->user = strdup(userp);
510
511 if(!digest->user) {
512 free(output_token);
513 return CURLE_OUT_OF_MEMORY;
514 }
515 }
516
517 if(passwdp) {
518 digest->passwd = strdup(passwdp);
519
520 if(!digest->passwd) {
521 free(output_token);
522 Curl_safefree(digest->user);
523 return CURLE_OUT_OF_MEMORY;
524 }
525 }
526
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);
535 free(output_token);
536
537 return CURLE_LOGIN_DENIED;
538 }
539
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;
553
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);
561
562 spn = curlx_convert_UTF8_to_tchar((char *) uripath);
563 if(!spn) {
564 s_pSecFn->FreeCredentialsHandle(&credentials);
565
566 Curl_sspi_free_identity(p_identity);
567 free(output_token);
568
569 return CURLE_OUT_OF_MEMORY;
570 }
571
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;
576
577 /* Generate our response message */
578 status = s_pSecFn->InitializeSecurityContext(&credentials, NULL,
579 spn,
580 ISC_REQ_USE_HTTP_STYLE, 0, 0,
581 &chlg_desc, 0,
582 digest->http_context,
583 &resp_desc, &attrs, &expiry);
584 curlx_unicodefree(spn);
585
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];
592#endif
593
594 s_pSecFn->FreeCredentialsHandle(&credentials);
595
596 Curl_sspi_free_identity(p_identity);
597 free(output_token);
598
599 Curl_safefree(digest->http_context);
600
601 if(status == SEC_E_INSUFFICIENT_MEMORY)
602 return CURLE_OUT_OF_MEMORY;
603
604 infof(data, "schannel: InitializeSecurityContext failed: %s",
605 Curl_sspi_strerror(status, buffer, sizeof(buffer)));
606
607 return CURLE_AUTH_ERROR;
608 }
609
610 output_token_len = resp_buf.cbBuffer;
611
612 s_pSecFn->FreeCredentialsHandle(&credentials);
613 Curl_sspi_free_identity(p_identity);
614 }
615
616 resp = malloc(output_token_len + 1);
617 if(!resp) {
618 free(output_token);
619
620 return CURLE_OUT_OF_MEMORY;
621 }
622
623 /* Copy the generated response */
624 memcpy(resp, output_token, output_token_len);
625 resp[output_token_len] = 0;
626
627 /* Return the response */
628 *outptr = resp;
629 *outlen = output_token_len;
630
631 /* Free the response buffer */
632 free(output_token);
633
634 return CURLE_OK;
635}
636
637/*
638 * Curl_auth_digest_cleanup()
639 *
640 * This is used to clean up the digest specific data.
641 *
642 * Parameters:
643 *
644 * digest [in/out] - The digest data struct being cleaned up.
645 *
646 */
647void Curl_auth_digest_cleanup(struct digestdata *digest)
648{
649 /* Free the input token */
650 Curl_safefree(digest->input_token);
651
652 /* Reset any variables */
653 digest->input_token_len = 0;
654
655 /* Delete security context */
656 if(digest->http_context) {
657 s_pSecFn->DeleteSecurityContext(digest->http_context);
658 Curl_safefree(digest->http_context);
659 }
660
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);
664}
665
666#endif /* USE_WINDOWS_SSPI && !CURL_DISABLE_CRYPTO_AUTH */
667