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