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