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