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