• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  * RFC2831 DIGEST-MD5 authentication
24  * RFC7616 DIGEST-SHA256, DIGEST-SHA512-256 authentication
25  *
26  ***************************************************************************/
27 
28 #include "curl_setup.h"
29 
30 #ifndef CURL_DISABLE_DIGEST_AUTH
31 
32 #include <curl/curl.h>
33 
34 #include "vauth/vauth.h"
35 #include "vauth/digest.h"
36 #include "urldata.h"
37 #include "curl_base64.h"
38 #include "curl_hmac.h"
39 #include "curl_md5.h"
40 #include "curl_sha256.h"
41 #include "vtls/vtls.h"
42 #include "warnless.h"
43 #include "strtok.h"
44 #include "strcase.h"
45 #include "curl_printf.h"
46 #include "rand.h"
47 
48 /* The last #include files should be: */
49 #include "curl_memory.h"
50 #include "memdebug.h"
51 
52 #define SESSION_ALGO 1 /* for algos with this bit set */
53 
54 #define ALGO_MD5 0
55 #define ALGO_MD5SESS (ALGO_MD5 | SESSION_ALGO)
56 #define ALGO_SHA256 2
57 #define ALGO_SHA256SESS (ALGO_SHA256 | SESSION_ALGO)
58 #define ALGO_SHA512_256 4
59 #define ALGO_SHA512_256SESS (ALGO_SHA512_256 | SESSION_ALGO)
60 
61 #if !defined(USE_WINDOWS_SSPI)
62 #define DIGEST_QOP_VALUE_AUTH             (1 << 0)
63 #define DIGEST_QOP_VALUE_AUTH_INT         (1 << 1)
64 #define DIGEST_QOP_VALUE_AUTH_CONF        (1 << 2)
65 
66 #define DIGEST_QOP_VALUE_STRING_AUTH      "auth"
67 #define DIGEST_QOP_VALUE_STRING_AUTH_INT  "auth-int"
68 #define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf"
69 #endif
70 
Curl_auth_digest_get_pair(const char * str,char * value,char * content,const char ** endptr)71 bool Curl_auth_digest_get_pair(const char *str, char *value, char *content,
72                                const char **endptr)
73 {
74   int c;
75   bool starts_with_quote = FALSE;
76   bool escape = FALSE;
77 
78   for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--);)
79     *value++ = *str++;
80   *value = 0;
81 
82   if('=' != *str++)
83     /* eek, no match */
84     return FALSE;
85 
86   if('\"' == *str) {
87     /* This starts with a quote so it must end with one as well! */
88     str++;
89     starts_with_quote = TRUE;
90   }
91 
92   for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) {
93     if(!escape) {
94       switch(*str) {
95       case '\\':
96         if(starts_with_quote) {
97           /* the start of an escaped quote */
98           escape = TRUE;
99           continue;
100         }
101         break;
102 
103       case ',':
104         if(!starts_with_quote) {
105           /* This signals the end of the content if we didn't get a starting
106              quote and then we do "sloppy" parsing */
107           c = 0; /* the end */
108           continue;
109         }
110         break;
111 
112       case '\r':
113       case '\n':
114         /* end of string */
115         if(starts_with_quote)
116           return FALSE; /* No closing quote */
117         c = 0;
118         continue;
119 
120       case '\"':
121         if(starts_with_quote) {
122           /* end of string */
123           c = 0;
124           continue;
125         }
126         else
127           return FALSE;
128       }
129     }
130 
131     escape = FALSE;
132     *content++ = *str;
133   }
134   if(escape)
135     return FALSE; /* No character after backslash */
136 
137   *content = 0;
138   *endptr = str;
139 
140   return TRUE;
141 }
142 
143 #if !defined(USE_WINDOWS_SSPI)
144 /* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string */
auth_digest_md5_to_ascii(unsigned char * source,unsigned char * dest)145 static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */
146                                      unsigned char *dest) /* 33 bytes */
147 {
148   int i;
149   for(i = 0; i < 16; i++)
150     msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]);
151 }
152 
153 /* Convert sha256 chunk to RFC7616 -suitable ascii string */
auth_digest_sha256_to_ascii(unsigned char * source,unsigned char * dest)154 static void auth_digest_sha256_to_ascii(unsigned char *source, /* 32 bytes */
155                                      unsigned char *dest) /* 65 bytes */
156 {
157   int i;
158   for(i = 0; i < 32; i++)
159     msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]);
160 }
161 
162 /* Perform quoted-string escaping as described in RFC2616 and its errata */
auth_digest_string_quoted(const char * source)163 static char *auth_digest_string_quoted(const char *source)
164 {
165   char *dest;
166   const char *s = source;
167   size_t n = 1; /* null terminator */
168 
169   /* Calculate size needed */
170   while(*s) {
171     ++n;
172     if(*s == '"' || *s == '\\') {
173       ++n;
174     }
175     ++s;
176   }
177 
178   dest = malloc(n);
179   if(dest) {
180     char *d = dest;
181     s = source;
182     while(*s) {
183       if(*s == '"' || *s == '\\') {
184         *d++ = '\\';
185       }
186       *d++ = *s++;
187     }
188     *d = '\0';
189   }
190 
191   return dest;
192 }
193 
194 /* Retrieves the value for a corresponding key from the challenge string
195  * returns TRUE if the key could be found, FALSE if it does not exists
196  */
auth_digest_get_key_value(const char * chlg,const char * key,char * value,size_t max_val_len,char end_char)197 static bool auth_digest_get_key_value(const char *chlg,
198                                       const char *key,
199                                       char *value,
200                                       size_t max_val_len,
201                                       char end_char)
202 {
203   char *find_pos;
204   size_t i;
205 
206   find_pos = strstr(chlg, key);
207   if(!find_pos)
208     return FALSE;
209 
210   find_pos += strlen(key);
211 
212   for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i)
213     value[i] = *find_pos++;
214   value[i] = '\0';
215 
216   return TRUE;
217 }
218 
auth_digest_get_qop_values(const char * options,int * value)219 static CURLcode auth_digest_get_qop_values(const char *options, int *value)
220 {
221   char *tmp;
222   char *token;
223   char *tok_buf = NULL;
224 
225   /* Initialise the output */
226   *value = 0;
227 
228   /* Tokenise the list of qop values. Use a temporary clone of the buffer since
229      strtok_r() ruins it. */
230   tmp = strdup(options);
231   if(!tmp)
232     return CURLE_OUT_OF_MEMORY;
233 
234   token = strtok_r(tmp, ",", &tok_buf);
235   while(token) {
236     if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH))
237       *value |= DIGEST_QOP_VALUE_AUTH;
238     else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT))
239       *value |= DIGEST_QOP_VALUE_AUTH_INT;
240     else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF))
241       *value |= DIGEST_QOP_VALUE_AUTH_CONF;
242 
243     token = strtok_r(NULL, ",", &tok_buf);
244   }
245 
246   free(tmp);
247 
248   return CURLE_OK;
249 }
250 
251 /*
252  * auth_decode_digest_md5_message()
253  *
254  * This is used internally to decode an already encoded DIGEST-MD5 challenge
255  * message into the separate attributes.
256  *
257  * Parameters:
258  *
259  * chlgref [in]     - The challenge message.
260  * nonce   [in/out] - The buffer where the nonce will be stored.
261  * nlen    [in]     - The length of the nonce buffer.
262  * realm   [in/out] - The buffer where the realm will be stored.
263  * rlen    [in]     - The length of the realm buffer.
264  * alg     [in/out] - The buffer where the algorithm will be stored.
265  * alen    [in]     - The length of the algorithm buffer.
266  * qop     [in/out] - The buffer where the qop-options will be stored.
267  * qlen    [in]     - The length of the qop buffer.
268  *
269  * Returns CURLE_OK on success.
270  */
auth_decode_digest_md5_message(const struct bufref * chlgref,char * nonce,size_t nlen,char * realm,size_t rlen,char * alg,size_t alen,char * qop,size_t qlen)271 static CURLcode auth_decode_digest_md5_message(const struct bufref *chlgref,
272                                                char *nonce, size_t nlen,
273                                                char *realm, size_t rlen,
274                                                char *alg, size_t alen,
275                                                char *qop, size_t qlen)
276 {
277   const char *chlg = (const char *) Curl_bufref_ptr(chlgref);
278 
279   /* Ensure we have a valid challenge message */
280   if(!Curl_bufref_len(chlgref))
281     return CURLE_BAD_CONTENT_ENCODING;
282 
283   /* Retrieve nonce string from the challenge */
284   if(!auth_digest_get_key_value(chlg, "nonce=\"", nonce, nlen, '\"'))
285     return CURLE_BAD_CONTENT_ENCODING;
286 
287   /* Retrieve realm string from the challenge */
288   if(!auth_digest_get_key_value(chlg, "realm=\"", realm, rlen, '\"')) {
289     /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
290     strcpy(realm, "");
291   }
292 
293   /* Retrieve algorithm string from the challenge */
294   if(!auth_digest_get_key_value(chlg, "algorithm=", alg, alen, ','))
295     return CURLE_BAD_CONTENT_ENCODING;
296 
297   /* Retrieve qop-options string from the challenge */
298   if(!auth_digest_get_key_value(chlg, "qop=\"", qop, qlen, '\"'))
299     return CURLE_BAD_CONTENT_ENCODING;
300 
301   return CURLE_OK;
302 }
303 
304 /*
305  * Curl_auth_is_digest_supported()
306  *
307  * This is used to evaluate if DIGEST is supported.
308  *
309  * Parameters: None
310  *
311  * Returns TRUE as DIGEST as handled by libcurl.
312  */
Curl_auth_is_digest_supported(void)313 bool Curl_auth_is_digest_supported(void)
314 {
315   return TRUE;
316 }
317 
318 /*
319  * Curl_auth_create_digest_md5_message()
320  *
321  * This is used to generate an already encoded DIGEST-MD5 response message
322  * ready for sending to the recipient.
323  *
324  * Parameters:
325  *
326  * data    [in]     - The session handle.
327  * chlg    [in]     - The challenge message.
328  * userp   [in]     - The user name.
329  * passwdp [in]     - The user's password.
330  * service [in]     - The service type such as http, smtp, pop or imap.
331  * out     [out]    - The result storage.
332  *
333  * Returns CURLE_OK on success.
334  */
Curl_auth_create_digest_md5_message(struct Curl_easy * data,const struct bufref * chlg,const char * userp,const char * passwdp,const char * service,struct bufref * out)335 CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data,
336                                              const struct bufref *chlg,
337                                              const char *userp,
338                                              const char *passwdp,
339                                              const char *service,
340                                              struct bufref *out)
341 {
342   size_t i;
343   struct MD5_context *ctxt;
344   char *response = NULL;
345   unsigned char digest[MD5_DIGEST_LEN];
346   char HA1_hex[2 * MD5_DIGEST_LEN + 1];
347   char HA2_hex[2 * MD5_DIGEST_LEN + 1];
348   char resp_hash_hex[2 * MD5_DIGEST_LEN + 1];
349   char nonce[64];
350   char realm[128];
351   char algorithm[64];
352   char qop_options[64];
353   int qop_values;
354   char cnonce[33];
355   char nonceCount[] = "00000001";
356   char method[]     = "AUTHENTICATE";
357   char qop[]        = DIGEST_QOP_VALUE_STRING_AUTH;
358   char *spn         = NULL;
359 
360   /* Decode the challenge message */
361   CURLcode result = auth_decode_digest_md5_message(chlg,
362                                                    nonce, sizeof(nonce),
363                                                    realm, sizeof(realm),
364                                                    algorithm,
365                                                    sizeof(algorithm),
366                                                    qop_options,
367                                                    sizeof(qop_options));
368   if(result)
369     return result;
370 
371   /* We only support md5 sessions */
372   if(strcmp(algorithm, "md5-sess") != 0)
373     return CURLE_BAD_CONTENT_ENCODING;
374 
375   /* Get the qop-values from the qop-options */
376   result = auth_digest_get_qop_values(qop_options, &qop_values);
377   if(result)
378     return result;
379 
380   /* We only support auth quality-of-protection */
381   if(!(qop_values & DIGEST_QOP_VALUE_AUTH))
382     return CURLE_BAD_CONTENT_ENCODING;
383 
384   /* Generate 32 random hex chars, 32 bytes + 1 null-termination */
385   result = Curl_rand_hex(data, (unsigned char *)cnonce, sizeof(cnonce));
386   if(result)
387     return result;
388 
389   /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
390   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
391   if(!ctxt)
392     return CURLE_OUT_OF_MEMORY;
393 
394   Curl_MD5_update(ctxt, (const unsigned char *) userp,
395                   curlx_uztoui(strlen(userp)));
396   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
397   Curl_MD5_update(ctxt, (const unsigned char *) realm,
398                   curlx_uztoui(strlen(realm)));
399   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
400   Curl_MD5_update(ctxt, (const unsigned char *) passwdp,
401                   curlx_uztoui(strlen(passwdp)));
402   Curl_MD5_final(ctxt, digest);
403 
404   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
405   if(!ctxt)
406     return CURLE_OUT_OF_MEMORY;
407 
408   Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN);
409   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
410   Curl_MD5_update(ctxt, (const unsigned char *) nonce,
411                   curlx_uztoui(strlen(nonce)));
412   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
413   Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
414                   curlx_uztoui(strlen(cnonce)));
415   Curl_MD5_final(ctxt, digest);
416 
417   /* Convert calculated 16 octet hex into 32 bytes string */
418   for(i = 0; i < MD5_DIGEST_LEN; i++)
419     msnprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]);
420 
421   /* Generate our SPN */
422   spn = Curl_auth_build_spn(service, data->conn->host.name, NULL);
423   if(!spn)
424     return CURLE_OUT_OF_MEMORY;
425 
426   /* Calculate H(A2) */
427   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
428   if(!ctxt) {
429     free(spn);
430 
431     return CURLE_OUT_OF_MEMORY;
432   }
433 
434   Curl_MD5_update(ctxt, (const unsigned char *) method,
435                   curlx_uztoui(strlen(method)));
436   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
437   Curl_MD5_update(ctxt, (const unsigned char *) spn,
438                   curlx_uztoui(strlen(spn)));
439   Curl_MD5_final(ctxt, digest);
440 
441   for(i = 0; i < MD5_DIGEST_LEN; i++)
442     msnprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]);
443 
444   /* Now calculate the response hash */
445   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
446   if(!ctxt) {
447     free(spn);
448 
449     return CURLE_OUT_OF_MEMORY;
450   }
451 
452   Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN);
453   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
454   Curl_MD5_update(ctxt, (const unsigned char *) nonce,
455                   curlx_uztoui(strlen(nonce)));
456   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
457 
458   Curl_MD5_update(ctxt, (const unsigned char *) nonceCount,
459                   curlx_uztoui(strlen(nonceCount)));
460   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
461   Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
462                   curlx_uztoui(strlen(cnonce)));
463   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
464   Curl_MD5_update(ctxt, (const unsigned char *) qop,
465                   curlx_uztoui(strlen(qop)));
466   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
467 
468   Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN);
469   Curl_MD5_final(ctxt, digest);
470 
471   for(i = 0; i < MD5_DIGEST_LEN; i++)
472     msnprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]);
473 
474   /* Generate the response */
475   response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\","
476                      "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s,"
477                      "qop=%s",
478                      userp, realm, nonce,
479                      cnonce, nonceCount, spn, resp_hash_hex, qop);
480   free(spn);
481   if(!response)
482     return CURLE_OUT_OF_MEMORY;
483 
484   /* Return the response. */
485   Curl_bufref_set(out, response, strlen(response), curl_free);
486   return result;
487 }
488 
489 /*
490  * Curl_auth_decode_digest_http_message()
491  *
492  * This is used to decode an HTTP DIGEST challenge message into the separate
493  * attributes.
494  *
495  * Parameters:
496  *
497  * chlg    [in]     - The challenge message.
498  * digest  [in/out] - The digest data struct being used and modified.
499  *
500  * Returns CURLE_OK on success.
501  */
Curl_auth_decode_digest_http_message(const char * chlg,struct digestdata * digest)502 CURLcode Curl_auth_decode_digest_http_message(const char *chlg,
503                                               struct digestdata *digest)
504 {
505   bool before = FALSE; /* got a nonce before */
506   bool foundAuth = FALSE;
507   bool foundAuthInt = FALSE;
508   char *token = NULL;
509   char *tmp = NULL;
510 
511   /* If we already have received a nonce, keep that in mind */
512   if(digest->nonce)
513     before = TRUE;
514 
515   /* Clean up any former leftovers and initialise to defaults */
516   Curl_auth_digest_cleanup(digest);
517 
518   for(;;) {
519     char value[DIGEST_MAX_VALUE_LENGTH];
520     char content[DIGEST_MAX_CONTENT_LENGTH];
521 
522     /* Pass all additional spaces here */
523     while(*chlg && ISBLANK(*chlg))
524       chlg++;
525 
526     /* Extract a value=content pair */
527     if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) {
528       if(strcasecompare(value, "nonce")) {
529         free(digest->nonce);
530         digest->nonce = strdup(content);
531         if(!digest->nonce)
532           return CURLE_OUT_OF_MEMORY;
533       }
534       else if(strcasecompare(value, "stale")) {
535         if(strcasecompare(content, "true")) {
536           digest->stale = TRUE;
537           digest->nc = 1; /* we make a new nonce now */
538         }
539       }
540       else if(strcasecompare(value, "realm")) {
541         free(digest->realm);
542         digest->realm = strdup(content);
543         if(!digest->realm)
544           return CURLE_OUT_OF_MEMORY;
545       }
546       else if(strcasecompare(value, "opaque")) {
547         free(digest->opaque);
548         digest->opaque = strdup(content);
549         if(!digest->opaque)
550           return CURLE_OUT_OF_MEMORY;
551       }
552       else if(strcasecompare(value, "qop")) {
553         char *tok_buf = NULL;
554         /* Tokenize the list and choose auth if possible, use a temporary
555            clone of the buffer since strtok_r() ruins it */
556         tmp = strdup(content);
557         if(!tmp)
558           return CURLE_OUT_OF_MEMORY;
559 
560         token = strtok_r(tmp, ",", &tok_buf);
561         while(token) {
562           /* Pass additional spaces here */
563           while(*token && ISBLANK(*token))
564             token++;
565           if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) {
566             foundAuth = TRUE;
567           }
568           else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) {
569             foundAuthInt = TRUE;
570           }
571           token = strtok_r(NULL, ",", &tok_buf);
572         }
573 
574         free(tmp);
575 
576         /* Select only auth or auth-int. Otherwise, ignore */
577         if(foundAuth) {
578           free(digest->qop);
579           digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH);
580           if(!digest->qop)
581             return CURLE_OUT_OF_MEMORY;
582         }
583         else if(foundAuthInt) {
584           free(digest->qop);
585           digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT);
586           if(!digest->qop)
587             return CURLE_OUT_OF_MEMORY;
588         }
589       }
590       else if(strcasecompare(value, "algorithm")) {
591         free(digest->algorithm);
592         digest->algorithm = strdup(content);
593         if(!digest->algorithm)
594           return CURLE_OUT_OF_MEMORY;
595 
596         if(strcasecompare(content, "MD5-sess"))
597           digest->algo = ALGO_MD5SESS;
598         else if(strcasecompare(content, "MD5"))
599           digest->algo = ALGO_MD5;
600         else if(strcasecompare(content, "SHA-256"))
601           digest->algo = ALGO_SHA256;
602         else if(strcasecompare(content, "SHA-256-SESS"))
603           digest->algo = ALGO_SHA256SESS;
604         else if(strcasecompare(content, "SHA-512-256"))
605           digest->algo = ALGO_SHA512_256;
606         else if(strcasecompare(content, "SHA-512-256-SESS"))
607           digest->algo = ALGO_SHA512_256SESS;
608         else
609           return CURLE_BAD_CONTENT_ENCODING;
610       }
611       else if(strcasecompare(value, "userhash")) {
612         if(strcasecompare(content, "true")) {
613           digest->userhash = TRUE;
614         }
615       }
616       else {
617         /* Unknown specifier, ignore it! */
618       }
619     }
620     else
621       break; /* We're done here */
622 
623     /* Pass all additional spaces here */
624     while(*chlg && ISBLANK(*chlg))
625       chlg++;
626 
627     /* Allow the list to be comma-separated */
628     if(',' == *chlg)
629       chlg++;
630   }
631 
632   /* We had a nonce since before, and we got another one now without
633      'stale=true'. This means we provided bad credentials in the previous
634      request */
635   if(before && !digest->stale)
636     return CURLE_BAD_CONTENT_ENCODING;
637 
638   /* We got this header without a nonce, that's a bad Digest line! */
639   if(!digest->nonce)
640     return CURLE_BAD_CONTENT_ENCODING;
641 
642   /* "<algo>-sess" protocol versions require "auth" or "auth-int" qop */
643   if(!digest->qop && (digest->algo & SESSION_ALGO))
644     return CURLE_BAD_CONTENT_ENCODING;
645 
646   return CURLE_OK;
647 }
648 
649 /*
650  * auth_create_digest_http_message()
651  *
652  * This is used to generate an HTTP DIGEST response message ready for sending
653  * to the recipient.
654  *
655  * Parameters:
656  *
657  * data    [in]     - The session handle.
658  * userp   [in]     - The user name.
659  * passwdp [in]     - The user's password.
660  * request [in]     - The HTTP request.
661  * uripath [in]     - The path of the HTTP uri.
662  * digest  [in/out] - The digest data struct being used and modified.
663  * outptr  [in/out] - The address where a pointer to newly allocated memory
664  *                    holding the result will be stored upon completion.
665  * outlen  [out]    - The length of the output message.
666  *
667  * Returns CURLE_OK on success.
668  */
auth_create_digest_http_message(struct Curl_easy * data,const char * userp,const char * passwdp,const unsigned char * request,const unsigned char * uripath,struct digestdata * digest,char ** outptr,size_t * outlen,void (* convert_to_ascii)(unsigned char *,unsigned char *),CURLcode (* hash)(unsigned char *,const unsigned char *,const size_t))669 static CURLcode auth_create_digest_http_message(
670                   struct Curl_easy *data,
671                   const char *userp,
672                   const char *passwdp,
673                   const unsigned char *request,
674                   const unsigned char *uripath,
675                   struct digestdata *digest,
676                   char **outptr, size_t *outlen,
677                   void (*convert_to_ascii)(unsigned char *, unsigned char *),
678                   CURLcode (*hash)(unsigned char *, const unsigned char *,
679                                    const size_t))
680 {
681   CURLcode result;
682   unsigned char hashbuf[32]; /* 32 bytes/256 bits */
683   unsigned char request_digest[65];
684   unsigned char ha1[65];    /* 64 digits and 1 zero byte */
685   unsigned char ha2[65];    /* 64 digits and 1 zero byte */
686   char userh[65];
687   char *cnonce = NULL;
688   size_t cnonce_sz = 0;
689   char *userp_quoted;
690   char *realm_quoted;
691   char *nonce_quoted;
692   char *response = NULL;
693   char *hashthis = NULL;
694   char *tmp = NULL;
695 
696   memset(hashbuf, 0, sizeof(hashbuf));
697   if(!digest->nc)
698     digest->nc = 1;
699 
700   if(!digest->cnonce) {
701     char cnoncebuf[33];
702     result = Curl_rand_hex(data, (unsigned char *)cnoncebuf,
703                            sizeof(cnoncebuf));
704     if(result)
705       return result;
706 
707     result = Curl_base64_encode(cnoncebuf, strlen(cnoncebuf),
708                                 &cnonce, &cnonce_sz);
709     if(result)
710       return result;
711 
712     digest->cnonce = cnonce;
713   }
714 
715   if(digest->userhash) {
716     hashthis = aprintf("%s:%s", userp, digest->realm ? digest->realm : "");
717     if(!hashthis)
718       return CURLE_OUT_OF_MEMORY;
719 
720     hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
721     free(hashthis);
722     convert_to_ascii(hashbuf, (unsigned char *)userh);
723   }
724 
725   /*
726     If the algorithm is "MD5" or unspecified (which then defaults to MD5):
727 
728       A1 = unq(username-value) ":" unq(realm-value) ":" passwd
729 
730     If the algorithm is "MD5-sess" then:
731 
732       A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":"
733            unq(nonce-value) ":" unq(cnonce-value)
734   */
735 
736   hashthis = aprintf("%s:%s:%s", userp, digest->realm ? digest->realm : "",
737                      passwdp);
738   if(!hashthis)
739     return CURLE_OUT_OF_MEMORY;
740 
741   hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
742   free(hashthis);
743   convert_to_ascii(hashbuf, ha1);
744 
745   if(digest->algo & SESSION_ALGO) {
746     /* nonce and cnonce are OUTSIDE the hash */
747     tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
748     if(!tmp)
749       return CURLE_OUT_OF_MEMORY;
750 
751     hash(hashbuf, (unsigned char *) tmp, strlen(tmp));
752     free(tmp);
753     convert_to_ascii(hashbuf, ha1);
754   }
755 
756   /*
757     If the "qop" directive's value is "auth" or is unspecified, then A2 is:
758 
759       A2 = Method ":" digest-uri-value
760 
761     If the "qop" value is "auth-int", then A2 is:
762 
763       A2 = Method ":" digest-uri-value ":" H(entity-body)
764 
765     (The "Method" value is the HTTP request method as specified in section
766     5.1.1 of RFC 2616)
767   */
768 
769   hashthis = aprintf("%s:%s", request, uripath);
770   if(!hashthis)
771     return CURLE_OUT_OF_MEMORY;
772 
773   if(digest->qop && strcasecompare(digest->qop, "auth-int")) {
774     /* We don't support auth-int for PUT or POST */
775     char hashed[65];
776     char *hashthis2;
777 
778     hash(hashbuf, (const unsigned char *)"", 0);
779     convert_to_ascii(hashbuf, (unsigned char *)hashed);
780 
781     hashthis2 = aprintf("%s:%s", hashthis, hashed);
782     free(hashthis);
783     hashthis = hashthis2;
784   }
785 
786   if(!hashthis)
787     return CURLE_OUT_OF_MEMORY;
788 
789   hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
790   free(hashthis);
791   convert_to_ascii(hashbuf, ha2);
792 
793   if(digest->qop) {
794     hashthis = aprintf("%s:%s:%08x:%s:%s:%s", ha1, digest->nonce, digest->nc,
795                        digest->cnonce, digest->qop, ha2);
796   }
797   else {
798     hashthis = aprintf("%s:%s:%s", ha1, digest->nonce, ha2);
799   }
800 
801   if(!hashthis)
802     return CURLE_OUT_OF_MEMORY;
803 
804   hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
805   free(hashthis);
806   convert_to_ascii(hashbuf, request_digest);
807 
808   /* For test case 64 (snooped from a Mozilla 1.3a request)
809 
810      Authorization: Digest username="testuser", realm="testrealm", \
811      nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
812 
813      Digest parameters are all quoted strings.  Username which is provided by
814      the user will need double quotes and backslashes within it escaped.
815      realm, nonce, and opaque will need backslashes as well as they were
816      de-escaped when copied from request header.  cnonce is generated with
817      web-safe characters.  uri is already percent encoded.  nc is 8 hex
818      characters.  algorithm and qop with standard values only contain web-safe
819      characters.
820   */
821   userp_quoted = auth_digest_string_quoted(digest->userhash ? userh : userp);
822   if(!userp_quoted)
823     return CURLE_OUT_OF_MEMORY;
824   if(digest->realm)
825     realm_quoted = auth_digest_string_quoted(digest->realm);
826   else {
827     realm_quoted = malloc(1);
828     if(realm_quoted)
829       realm_quoted[0] = 0;
830   }
831   if(!realm_quoted) {
832     free(userp_quoted);
833     return CURLE_OUT_OF_MEMORY;
834   }
835   nonce_quoted = auth_digest_string_quoted(digest->nonce);
836   if(!nonce_quoted) {
837     free(realm_quoted);
838     free(userp_quoted);
839     return CURLE_OUT_OF_MEMORY;
840   }
841 
842   if(digest->qop) {
843     response = aprintf("username=\"%s\", "
844                        "realm=\"%s\", "
845                        "nonce=\"%s\", "
846                        "uri=\"%s\", "
847                        "cnonce=\"%s\", "
848                        "nc=%08x, "
849                        "qop=%s, "
850                        "response=\"%s\"",
851                        userp_quoted,
852                        realm_quoted,
853                        nonce_quoted,
854                        uripath,
855                        digest->cnonce,
856                        digest->nc,
857                        digest->qop,
858                        request_digest);
859 
860     /* Increment nonce-count to use another nc value for the next request */
861     digest->nc++;
862   }
863   else {
864     response = aprintf("username=\"%s\", "
865                        "realm=\"%s\", "
866                        "nonce=\"%s\", "
867                        "uri=\"%s\", "
868                        "response=\"%s\"",
869                        userp_quoted,
870                        realm_quoted,
871                        nonce_quoted,
872                        uripath,
873                        request_digest);
874   }
875   free(nonce_quoted);
876   free(realm_quoted);
877   free(userp_quoted);
878   if(!response)
879     return CURLE_OUT_OF_MEMORY;
880 
881   /* Add the optional fields */
882   if(digest->opaque) {
883     char *opaque_quoted;
884     /* Append the opaque */
885     opaque_quoted = auth_digest_string_quoted(digest->opaque);
886     if(!opaque_quoted) {
887       free(response);
888       return CURLE_OUT_OF_MEMORY;
889     }
890     tmp = aprintf("%s, opaque=\"%s\"", response, opaque_quoted);
891     free(response);
892     free(opaque_quoted);
893     if(!tmp)
894       return CURLE_OUT_OF_MEMORY;
895 
896     response = tmp;
897   }
898 
899   if(digest->algorithm) {
900     /* Append the algorithm */
901     tmp = aprintf("%s, algorithm=%s", response, digest->algorithm);
902     free(response);
903     if(!tmp)
904       return CURLE_OUT_OF_MEMORY;
905 
906     response = tmp;
907   }
908 
909   if(digest->userhash) {
910     /* Append the userhash */
911     tmp = aprintf("%s, userhash=true", response);
912     free(response);
913     if(!tmp)
914       return CURLE_OUT_OF_MEMORY;
915 
916     response = tmp;
917   }
918 
919   /* Return the output */
920   *outptr = response;
921   *outlen = strlen(response);
922 
923   return CURLE_OK;
924 }
925 
926 /*
927  * Curl_auth_create_digest_http_message()
928  *
929  * This is used to generate an HTTP DIGEST response message ready for sending
930  * to the recipient.
931  *
932  * Parameters:
933  *
934  * data    [in]     - The session handle.
935  * userp   [in]     - The user name.
936  * passwdp [in]     - The user's password.
937  * request [in]     - The HTTP request.
938  * uripath [in]     - The path of the HTTP uri.
939  * digest  [in/out] - The digest data struct being used and modified.
940  * outptr  [in/out] - The address where a pointer to newly allocated memory
941  *                    holding the result will be stored upon completion.
942  * outlen  [out]    - The length of the output message.
943  *
944  * Returns CURLE_OK on success.
945  */
Curl_auth_create_digest_http_message(struct Curl_easy * data,const char * userp,const char * passwdp,const unsigned char * request,const unsigned char * uripath,struct digestdata * digest,char ** outptr,size_t * outlen)946 CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
947                                               const char *userp,
948                                               const char *passwdp,
949                                               const unsigned char *request,
950                                               const unsigned char *uripath,
951                                               struct digestdata *digest,
952                                               char **outptr, size_t *outlen)
953 {
954   if(digest->algo <= ALGO_MD5SESS)
955     return auth_create_digest_http_message(data, userp, passwdp,
956                                            request, uripath, digest,
957                                            outptr, outlen,
958                                            auth_digest_md5_to_ascii,
959                                            Curl_md5it);
960   DEBUGASSERT(digest->algo <= ALGO_SHA512_256SESS);
961   return auth_create_digest_http_message(data, userp, passwdp,
962                                          request, uripath, digest,
963                                          outptr, outlen,
964                                          auth_digest_sha256_to_ascii,
965                                          Curl_sha256it);
966 }
967 
968 /*
969  * Curl_auth_digest_cleanup()
970  *
971  * This is used to clean up the digest specific data.
972  *
973  * Parameters:
974  *
975  * digest    [in/out] - The digest data struct being cleaned up.
976  *
977  */
Curl_auth_digest_cleanup(struct digestdata * digest)978 void Curl_auth_digest_cleanup(struct digestdata *digest)
979 {
980   Curl_safefree(digest->nonce);
981   Curl_safefree(digest->cnonce);
982   Curl_safefree(digest->realm);
983   Curl_safefree(digest->opaque);
984   Curl_safefree(digest->qop);
985   Curl_safefree(digest->algorithm);
986 
987   digest->nc = 0;
988   digest->algo = ALGO_MD5; /* default algorithm */
989   digest->stale = FALSE; /* default means normal, not stale */
990   digest->userhash = FALSE;
991 }
992 #endif  /* !USE_WINDOWS_SSPI */
993 
994 #endif  /* !CURL_DISABLE_DIGEST_AUTH */
995