xref: /curl/lib/vauth/digest.c (revision fa9a5649)
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 didn't 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      strtok_r() ruins it. */
231   tmp = strdup(options);
232   if(!tmp)
233     return CURLE_OUT_OF_MEMORY;
234 
235   token = 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 = 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 user name.
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 strtok_r() ruins it */
557         tmp = strdup(content);
558         if(!tmp)
559           return CURLE_OUT_OF_MEMORY;
560 
561         token = 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 = 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're 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's 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 user name.
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[33];
713     result = Curl_rand_hex(data, (unsigned char *)cnoncebuf,
714                            sizeof(cnoncebuf));
715     if(result)
716       return result;
717 
718     result = Curl_base64_encode(cnoncebuf, strlen(cnoncebuf),
719                                 &cnonce, &cnonce_sz);
720     if(result)
721       return result;
722 
723     digest->cnonce = cnonce;
724   }
725 
726   if(digest->userhash) {
727     hashthis = aprintf("%s:%s", userp, digest->realm ? digest->realm : "");
728     if(!hashthis)
729       return CURLE_OUT_OF_MEMORY;
730 
731     result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
732     free(hashthis);
733     if(result)
734       return result;
735     convert_to_ascii(hashbuf, (unsigned char *)userh);
736   }
737 
738   /*
739     If the algorithm is "MD5" or unspecified (which then defaults to MD5):
740 
741       A1 = unq(username-value) ":" unq(realm-value) ":" passwd
742 
743     If the algorithm is "MD5-sess" then:
744 
745       A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":"
746            unq(nonce-value) ":" unq(cnonce-value)
747   */
748 
749   hashthis = aprintf("%s:%s:%s", userp, digest->realm ? digest->realm : "",
750                      passwdp);
751   if(!hashthis)
752     return CURLE_OUT_OF_MEMORY;
753 
754   result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
755   free(hashthis);
756   if(result)
757     return result;
758   convert_to_ascii(hashbuf, ha1);
759 
760   if(digest->algo & SESSION_ALGO) {
761     /* nonce and cnonce are OUTSIDE the hash */
762     tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
763     if(!tmp)
764       return CURLE_OUT_OF_MEMORY;
765 
766     result = hash(hashbuf, (unsigned char *) tmp, strlen(tmp));
767     free(tmp);
768     if(result)
769       return result;
770     convert_to_ascii(hashbuf, ha1);
771   }
772 
773   /*
774     If the "qop" directive's value is "auth" or is unspecified, then A2 is:
775 
776       A2 = Method ":" digest-uri-value
777 
778     If the "qop" value is "auth-int", then A2 is:
779 
780       A2 = Method ":" digest-uri-value ":" H(entity-body)
781 
782     (The "Method" value is the HTTP request method as specified in section
783     5.1.1 of RFC 2616)
784   */
785 
786   hashthis = aprintf("%s:%s", request, uripath);
787   if(!hashthis)
788     return CURLE_OUT_OF_MEMORY;
789 
790   if(digest->qop && strcasecompare(digest->qop, "auth-int")) {
791     /* We don't support auth-int for PUT or POST */
792     char hashed[65];
793     char *hashthis2;
794 
795     result = hash(hashbuf, (const unsigned char *)"", 0);
796     if(result) {
797       free(hashthis);
798       return result;
799     }
800     convert_to_ascii(hashbuf, (unsigned char *)hashed);
801 
802     hashthis2 = aprintf("%s:%s", hashthis, hashed);
803     free(hashthis);
804     hashthis = hashthis2;
805   }
806 
807   if(!hashthis)
808     return CURLE_OUT_OF_MEMORY;
809 
810   result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
811   free(hashthis);
812   if(result)
813     return result;
814   convert_to_ascii(hashbuf, ha2);
815 
816   if(digest->qop) {
817     hashthis = aprintf("%s:%s:%08x:%s:%s:%s", ha1, digest->nonce, digest->nc,
818                        digest->cnonce, digest->qop, ha2);
819   }
820   else {
821     hashthis = aprintf("%s:%s:%s", ha1, digest->nonce, ha2);
822   }
823 
824   if(!hashthis)
825     return CURLE_OUT_OF_MEMORY;
826 
827   result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
828   free(hashthis);
829   if(result)
830     return result;
831   convert_to_ascii(hashbuf, request_digest);
832 
833   /* For test case 64 (snooped from a Mozilla 1.3a request)
834 
835      Authorization: Digest username="testuser", realm="testrealm", \
836      nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
837 
838      Digest parameters are all quoted strings.  Username which is provided by
839      the user will need double quotes and backslashes within it escaped.
840      realm, nonce, and opaque will need backslashes as well as they were
841      de-escaped when copied from request header.  cnonce is generated with
842      web-safe characters.  uri is already percent encoded.  nc is 8 hex
843      characters.  algorithm and qop with standard values only contain web-safe
844      characters.
845   */
846   userp_quoted = auth_digest_string_quoted(digest->userhash ? userh : userp);
847   if(!userp_quoted)
848     return CURLE_OUT_OF_MEMORY;
849   if(digest->realm)
850     realm_quoted = auth_digest_string_quoted(digest->realm);
851   else {
852     realm_quoted = malloc(1);
853     if(realm_quoted)
854       realm_quoted[0] = 0;
855   }
856   if(!realm_quoted) {
857     free(userp_quoted);
858     return CURLE_OUT_OF_MEMORY;
859   }
860   nonce_quoted = auth_digest_string_quoted(digest->nonce);
861   if(!nonce_quoted) {
862     free(realm_quoted);
863     free(userp_quoted);
864     return CURLE_OUT_OF_MEMORY;
865   }
866 
867   if(digest->qop) {
868     response = aprintf("username=\"%s\", "
869                        "realm=\"%s\", "
870                        "nonce=\"%s\", "
871                        "uri=\"%s\", "
872                        "cnonce=\"%s\", "
873                        "nc=%08x, "
874                        "qop=%s, "
875                        "response=\"%s\"",
876                        userp_quoted,
877                        realm_quoted,
878                        nonce_quoted,
879                        uripath,
880                        digest->cnonce,
881                        digest->nc,
882                        digest->qop,
883                        request_digest);
884 
885     /* Increment nonce-count to use another nc value for the next request */
886     digest->nc++;
887   }
888   else {
889     response = aprintf("username=\"%s\", "
890                        "realm=\"%s\", "
891                        "nonce=\"%s\", "
892                        "uri=\"%s\", "
893                        "response=\"%s\"",
894                        userp_quoted,
895                        realm_quoted,
896                        nonce_quoted,
897                        uripath,
898                        request_digest);
899   }
900   free(nonce_quoted);
901   free(realm_quoted);
902   free(userp_quoted);
903   if(!response)
904     return CURLE_OUT_OF_MEMORY;
905 
906   /* Add the optional fields */
907   if(digest->opaque) {
908     char *opaque_quoted;
909     /* Append the opaque */
910     opaque_quoted = auth_digest_string_quoted(digest->opaque);
911     if(!opaque_quoted) {
912       free(response);
913       return CURLE_OUT_OF_MEMORY;
914     }
915     tmp = aprintf("%s, opaque=\"%s\"", response, opaque_quoted);
916     free(response);
917     free(opaque_quoted);
918     if(!tmp)
919       return CURLE_OUT_OF_MEMORY;
920 
921     response = tmp;
922   }
923 
924   if(digest->algorithm) {
925     /* Append the algorithm */
926     tmp = aprintf("%s, algorithm=%s", response, digest->algorithm);
927     free(response);
928     if(!tmp)
929       return CURLE_OUT_OF_MEMORY;
930 
931     response = tmp;
932   }
933 
934   if(digest->userhash) {
935     /* Append the userhash */
936     tmp = aprintf("%s, userhash=true", response);
937     free(response);
938     if(!tmp)
939       return CURLE_OUT_OF_MEMORY;
940 
941     response = tmp;
942   }
943 
944   /* Return the output */
945   *outptr = response;
946   *outlen = strlen(response);
947 
948   return CURLE_OK;
949 }
950 
951 /*
952  * Curl_auth_create_digest_http_message()
953  *
954  * This is used to generate an HTTP DIGEST response message ready for sending
955  * to the recipient.
956  *
957  * Parameters:
958  *
959  * data    [in]     - The session handle.
960  * userp   [in]     - The user name.
961  * passwdp [in]     - The user's password.
962  * request [in]     - The HTTP request.
963  * uripath [in]     - The path of the HTTP uri.
964  * digest  [in/out] - The digest data struct being used and modified.
965  * outptr  [in/out] - The address where a pointer to newly allocated memory
966  *                    holding the result will be stored upon completion.
967  * outlen  [out]    - The length of the output message.
968  *
969  * Returns CURLE_OK on success.
970  */
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)971 CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
972                                               const char *userp,
973                                               const char *passwdp,
974                                               const unsigned char *request,
975                                               const unsigned char *uripath,
976                                               struct digestdata *digest,
977                                               char **outptr, size_t *outlen)
978 {
979   if(digest->algo <= ALGO_MD5SESS)
980     return auth_create_digest_http_message(data, userp, passwdp,
981                                            request, uripath, digest,
982                                            outptr, outlen,
983                                            auth_digest_md5_to_ascii,
984                                            Curl_md5it);
985 
986   if(digest->algo <= ALGO_SHA256SESS)
987     return auth_create_digest_http_message(data, userp, passwdp,
988                                            request, uripath, digest,
989                                            outptr, outlen,
990                                            auth_digest_sha256_to_ascii,
991                                            Curl_sha256it);
992 #ifdef CURL_HAVE_SHA512_256
993   if(digest->algo <= ALGO_SHA512_256SESS)
994     return auth_create_digest_http_message(data, userp, passwdp,
995                                            request, uripath, digest,
996                                            outptr, outlen,
997                                            auth_digest_sha256_to_ascii,
998                                            Curl_sha512_256it);
999 #endif /* CURL_HAVE_SHA512_256 */
1000 
1001   /* Should be unreachable */
1002   return CURLE_BAD_CONTENT_ENCODING;
1003 }
1004 
1005 /*
1006  * Curl_auth_digest_cleanup()
1007  *
1008  * This is used to clean up the digest specific data.
1009  *
1010  * Parameters:
1011  *
1012  * digest    [in/out] - The digest data struct being cleaned up.
1013  *
1014  */
Curl_auth_digest_cleanup(struct digestdata * digest)1015 void Curl_auth_digest_cleanup(struct digestdata *digest)
1016 {
1017   Curl_safefree(digest->nonce);
1018   Curl_safefree(digest->cnonce);
1019   Curl_safefree(digest->realm);
1020   Curl_safefree(digest->opaque);
1021   Curl_safefree(digest->qop);
1022   Curl_safefree(digest->algorithm);
1023 
1024   digest->nc = 0;
1025   digest->algo = ALGO_MD5; /* default algorithm */
1026   digest->stale = FALSE; /* default means normal, not stale */
1027   digest->userhash = FALSE;
1028 }
1029 #endif  /* !USE_WINDOWS_SSPI */
1030 
1031 #endif  /* !CURL_DISABLE_DIGEST_AUTH */
1032