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