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