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