f50f8033f9a68188258e581cd4aa1f5473442052
[platform/upstream/curl.git] / lib / http_digest.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2013, 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 http://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  ***************************************************************************/
22
23 #include "curl_setup.h"
24
25 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH)
26
27 #include "urldata.h"
28 #include "rawstr.h"
29 #include "curl_base64.h"
30 #include "curl_md5.h"
31 #include "http_digest.h"
32 #include "strtok.h"
33 #include "curl_memory.h"
34 #include "sslgen.h" /* for Curl_rand() */
35 #include "non-ascii.h" /* included for Curl_convert_... prototypes */
36 #include "warnless.h"
37
38 #define _MPRINTF_REPLACE /* use our functions only */
39 #include <curl/mprintf.h>
40
41 /* The last #include file should be: */
42 #include "memdebug.h"
43
44 #define MAX_VALUE_LENGTH 256
45 #define MAX_CONTENT_LENGTH 1024
46
47 static void digest_cleanup_one(struct digestdata *dig);
48
49 /*
50  * Return 0 on success and then the buffers are filled in fine.
51  *
52  * Non-zero means failure to parse.
53  */
54 static int get_pair(const char *str, char *value, char *content,
55                     const char **endptr)
56 {
57   int c;
58   bool starts_with_quote = FALSE;
59   bool escape = FALSE;
60
61   for(c=MAX_VALUE_LENGTH-1; (*str && (*str != '=') && c--); )
62     *value++ = *str++;
63   *value=0;
64
65   if('=' != *str++)
66     /* eek, no match */
67     return 1;
68
69   if('\"' == *str) {
70     /* this starts with a quote so it must end with one as well! */
71     str++;
72     starts_with_quote = TRUE;
73   }
74
75   for(c=MAX_CONTENT_LENGTH-1; *str && c--; str++) {
76     switch(*str) {
77     case '\\':
78       if(!escape) {
79         /* possibly the start of an escaped quote */
80         escape = TRUE;
81         *content++ = '\\'; /* even though this is an escape character, we still
82                               store it as-is in the target buffer */
83         continue;
84       }
85       break;
86     case ',':
87       if(!starts_with_quote) {
88         /* this signals the end of the content if we didn't get a starting
89            quote and then we do "sloppy" parsing */
90         c=0; /* the end */
91         continue;
92       }
93       break;
94     case '\r':
95     case '\n':
96       /* end of string */
97       c=0;
98       continue;
99     case '\"':
100       if(!escape && starts_with_quote) {
101         /* end of string */
102         c=0;
103         continue;
104       }
105       break;
106     }
107     escape = FALSE;
108     *content++ = *str;
109   }
110   *content=0;
111
112   *endptr = str;
113
114   return 0; /* all is fine! */
115 }
116
117 /* Test example headers:
118
119 WWW-Authenticate: Digest realm="testrealm", nonce="1053604598"
120 Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598"
121
122 */
123
124 CURLdigest Curl_input_digest(struct connectdata *conn,
125                              bool proxy,
126                              const char *header) /* rest of the *-authenticate:
127                                                     header */
128 {
129   char *token = NULL;
130   char *tmp = NULL;
131   bool foundAuth = FALSE;
132   bool foundAuthInt = FALSE;
133   struct SessionHandle *data=conn->data;
134   bool before = FALSE; /* got a nonce before */
135   struct digestdata *d;
136
137   if(proxy) {
138     d = &data->state.proxydigest;
139   }
140   else {
141     d = &data->state.digest;
142   }
143
144   /* skip initial whitespaces */
145   while(*header && ISSPACE(*header))
146     header++;
147
148   if(checkprefix("Digest", header)) {
149     header += strlen("Digest");
150
151     /* If we already have received a nonce, keep that in mind */
152     if(d->nonce)
153       before = TRUE;
154
155     /* clear off any former leftovers and init to defaults */
156     digest_cleanup_one(d);
157
158     for(;;) {
159       char value[MAX_VALUE_LENGTH];
160       char content[MAX_CONTENT_LENGTH];
161
162       while(*header && ISSPACE(*header))
163         header++;
164
165       /* extract a value=content pair */
166       if(!get_pair(header, value, content, &header)) {
167         if(Curl_raw_equal(value, "nonce")) {
168           d->nonce = strdup(content);
169           if(!d->nonce)
170             return CURLDIGEST_NOMEM;
171         }
172         else if(Curl_raw_equal(value, "stale")) {
173           if(Curl_raw_equal(content, "true")) {
174             d->stale = TRUE;
175             d->nc = 1; /* we make a new nonce now */
176           }
177         }
178         else if(Curl_raw_equal(value, "realm")) {
179           d->realm = strdup(content);
180           if(!d->realm)
181             return CURLDIGEST_NOMEM;
182         }
183         else if(Curl_raw_equal(value, "opaque")) {
184           d->opaque = strdup(content);
185           if(!d->opaque)
186             return CURLDIGEST_NOMEM;
187         }
188         else if(Curl_raw_equal(value, "qop")) {
189           char *tok_buf;
190           /* tokenize the list and choose auth if possible, use a temporary
191              clone of the buffer since strtok_r() ruins it */
192           tmp = strdup(content);
193           if(!tmp)
194             return CURLDIGEST_NOMEM;
195           token = strtok_r(tmp, ",", &tok_buf);
196           while(token != NULL) {
197             if(Curl_raw_equal(token, "auth")) {
198               foundAuth = TRUE;
199             }
200             else if(Curl_raw_equal(token, "auth-int")) {
201               foundAuthInt = TRUE;
202             }
203             token = strtok_r(NULL, ",", &tok_buf);
204           }
205           free(tmp);
206           /*select only auth o auth-int. Otherwise, ignore*/
207           if(foundAuth) {
208             d->qop = strdup("auth");
209             if(!d->qop)
210               return CURLDIGEST_NOMEM;
211           }
212           else if(foundAuthInt) {
213             d->qop = strdup("auth-int");
214             if(!d->qop)
215               return CURLDIGEST_NOMEM;
216           }
217         }
218         else if(Curl_raw_equal(value, "algorithm")) {
219           d->algorithm = strdup(content);
220           if(!d->algorithm)
221             return CURLDIGEST_NOMEM;
222           if(Curl_raw_equal(content, "MD5-sess"))
223             d->algo = CURLDIGESTALGO_MD5SESS;
224           else if(Curl_raw_equal(content, "MD5"))
225             d->algo = CURLDIGESTALGO_MD5;
226           else
227             return CURLDIGEST_BADALGO;
228         }
229         else {
230           /* unknown specifier, ignore it! */
231         }
232       }
233       else
234         break; /* we're done here */
235
236       /* pass all additional spaces here */
237       while(*header && ISSPACE(*header))
238         header++;
239       if(',' == *header)
240         /* allow the list to be comma-separated */
241         header++;
242     }
243     /* We had a nonce since before, and we got another one now without
244        'stale=true'. This means we provided bad credentials in the previous
245        request */
246     if(before && !d->stale)
247       return CURLDIGEST_BAD;
248
249     /* We got this header without a nonce, that's a bad Digest line! */
250     if(!d->nonce)
251       return CURLDIGEST_BAD;
252   }
253   else
254     /* else not a digest, get out */
255     return CURLDIGEST_NONE;
256
257   return CURLDIGEST_FINE;
258 }
259
260 /* convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/
261 static void md5_to_ascii(unsigned char *source, /* 16 bytes */
262                          unsigned char *dest) /* 33 bytes */
263 {
264   int i;
265   for(i=0; i<16; i++)
266     snprintf((char *)&dest[i*2], 3, "%02x", source[i]);
267 }
268
269 /* Perform quoted-string escaping as described in RFC2616 and its errata */
270 static char *string_quoted(const char *source)
271 {
272   char *dest, *d;
273   const char *s = source;
274   size_t n = 1; /* null terminator */
275
276   /* Calculate size needed */
277   while(*s) {
278     ++n;
279     if(*s == '"' || *s == '\\') {
280       ++n;
281     }
282     ++s;
283   }
284
285   dest = malloc(n);
286   if(dest) {
287     s = source;
288     d = dest;
289     while(*s) {
290       if(*s == '"' || *s == '\\') {
291         *d++ = '\\';
292       }
293       *d++ = *s++;
294     }
295     *d = 0;
296   }
297
298   return dest;
299 }
300
301 CURLcode Curl_output_digest(struct connectdata *conn,
302                             bool proxy,
303                             const unsigned char *request,
304                             const unsigned char *uripath)
305 {
306   /* We have a Digest setup for this, use it!  Now, to get all the details for
307      this sorted out, I must urge you dear friend to read up on the RFC2617
308      section 3.2.2, */
309   unsigned char md5buf[16]; /* 16 bytes/128 bits */
310   unsigned char request_digest[33];
311   unsigned char *md5this;
312   unsigned char ha1[33];/* 32 digits and 1 zero byte */
313   unsigned char ha2[33];/* 32 digits and 1 zero byte */
314   char cnoncebuf[33];
315   char *cnonce = NULL;
316   size_t cnonce_sz = 0;
317   char *tmp = NULL;
318   char **allocuserpwd;
319   size_t userlen;
320   const char *userp;
321   char *userp_quoted;
322   const char *passwdp;
323   struct auth *authp;
324
325   struct SessionHandle *data = conn->data;
326   struct digestdata *d;
327   CURLcode rc;
328 /* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines.
329    It converts digest text to ASCII so the MD5 will be correct for
330    what ultimately goes over the network.
331 */
332 #define CURL_OUTPUT_DIGEST_CONV(a, b) \
333   rc = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \
334   if(rc != CURLE_OK) { \
335     free(b); \
336     return rc; \
337   }
338
339   if(proxy) {
340     d = &data->state.proxydigest;
341     allocuserpwd = &conn->allocptr.proxyuserpwd;
342     userp = conn->proxyuser;
343     passwdp = conn->proxypasswd;
344     authp = &data->state.authproxy;
345   }
346   else {
347     d = &data->state.digest;
348     allocuserpwd = &conn->allocptr.userpwd;
349     userp = conn->user;
350     passwdp = conn->passwd;
351     authp = &data->state.authhost;
352   }
353
354   Curl_safefree(*allocuserpwd);
355
356   /* not set means empty */
357   if(!userp)
358     userp="";
359
360   if(!passwdp)
361     passwdp="";
362
363   if(!d->nonce) {
364     authp->done = FALSE;
365     return CURLE_OK;
366   }
367   authp->done = TRUE;
368
369   if(!d->nc)
370     d->nc = 1;
371
372   if(!d->cnonce) {
373     struct timeval now = Curl_tvnow();
374     snprintf(cnoncebuf, sizeof(cnoncebuf), "%08x%08x%08x%08x",
375              Curl_rand(data), Curl_rand(data),
376              (unsigned int)now.tv_sec,
377              (unsigned int)now.tv_usec);
378
379     rc = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf),
380                             &cnonce, &cnonce_sz);
381     if(rc)
382       return rc;
383     d->cnonce = cnonce;
384   }
385
386   /*
387     if the algorithm is "MD5" or unspecified (which then defaults to MD5):
388
389     A1 = unq(username-value) ":" unq(realm-value) ":" passwd
390
391     if the algorithm is "MD5-sess" then:
392
393     A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
394          ":" unq(nonce-value) ":" unq(cnonce-value)
395   */
396
397   md5this = (unsigned char *)
398     aprintf("%s:%s:%s", userp, d->realm, passwdp);
399   if(!md5this)
400     return CURLE_OUT_OF_MEMORY;
401
402   CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
403   Curl_md5it(md5buf, md5this);
404   Curl_safefree(md5this);
405   md5_to_ascii(md5buf, ha1);
406
407   if(d->algo == CURLDIGESTALGO_MD5SESS) {
408     /* nonce and cnonce are OUTSIDE the hash */
409     tmp = aprintf("%s:%s:%s", ha1, d->nonce, d->cnonce);
410     if(!tmp)
411       return CURLE_OUT_OF_MEMORY;
412     CURL_OUTPUT_DIGEST_CONV(data, tmp); /* convert on non-ASCII machines */
413     Curl_md5it(md5buf, (unsigned char *)tmp);
414     Curl_safefree(tmp);
415     md5_to_ascii(md5buf, ha1);
416   }
417
418   /*
419     If the "qop" directive's value is "auth" or is unspecified, then A2 is:
420
421       A2       = Method ":" digest-uri-value
422
423           If the "qop" value is "auth-int", then A2 is:
424
425       A2       = Method ":" digest-uri-value ":" H(entity-body)
426
427     (The "Method" value is the HTTP request method as specified in section
428     5.1.1 of RFC 2616)
429   */
430
431   /* So IE browsers < v7 cut off the URI part at the query part when they
432      evaluate the MD5 and some (IIS?) servers work with them so we may need to
433      do the Digest IE-style. Note that the different ways cause different MD5
434      sums to get sent.
435
436      Apache servers can be set to do the Digest IE-style automatically using
437      the BrowserMatch feature:
438      http://httpd.apache.org/docs/2.2/mod/mod_auth_digest.html#msie
439
440      Further details on Digest implementation differences:
441      http://www.fngtps.com/2006/09/http-authentication
442   */
443   if(authp->iestyle && ((tmp = strchr((char *)uripath, '?')) != NULL)) {
444     md5this = (unsigned char *)aprintf("%s:%.*s", request,
445                                        curlx_sztosi(tmp - (char *)uripath),
446                                        uripath);
447   }
448   else
449     md5this = (unsigned char *)aprintf("%s:%s", request, uripath);
450
451   if(d->qop && Curl_raw_equal(d->qop, "auth-int")) {
452     /* We don't support auth-int for PUT or POST at the moment.
453        TODO: replace md5 of empty string with entity-body for PUT/POST */
454     unsigned char *md5this2 = (unsigned char *)
455       aprintf("%s:%s", md5this, "d41d8cd98f00b204e9800998ecf8427e");
456     Curl_safefree(md5this);
457     md5this = md5this2;
458   }
459
460   if(!md5this)
461     return CURLE_OUT_OF_MEMORY;
462
463   CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
464   Curl_md5it(md5buf, md5this);
465   Curl_safefree(md5this);
466   md5_to_ascii(md5buf, ha2);
467
468   if(d->qop) {
469     md5this = (unsigned char *)aprintf("%s:%s:%08x:%s:%s:%s",
470                                        ha1,
471                                        d->nonce,
472                                        d->nc,
473                                        d->cnonce,
474                                        d->qop,
475                                        ha2);
476   }
477   else {
478     md5this = (unsigned char *)aprintf("%s:%s:%s",
479                                        ha1,
480                                        d->nonce,
481                                        ha2);
482   }
483   if(!md5this)
484     return CURLE_OUT_OF_MEMORY;
485
486   CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
487   Curl_md5it(md5buf, md5this);
488   Curl_safefree(md5this);
489   md5_to_ascii(md5buf, request_digest);
490
491   /* for test case 64 (snooped from a Mozilla 1.3a request)
492
493     Authorization: Digest username="testuser", realm="testrealm", \
494     nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
495
496     Digest parameters are all quoted strings.  Username which is provided by
497     the user will need double quotes and backslashes within it escaped.  For
498     the other fields, this shouldn't be an issue.  realm, nonce, and opaque
499     are copied as is from the server, escapes and all.  cnonce is generated
500     with web-safe characters.  uri is already percent encoded.  nc is 8 hex
501     characters.  algorithm and qop with standard values only contain web-safe
502     chracters.
503   */
504   userp_quoted = string_quoted(userp);
505   if(!userp_quoted)
506     return CURLE_OUT_OF_MEMORY;
507
508   if(d->qop) {
509     *allocuserpwd =
510       aprintf( "%sAuthorization: Digest "
511                "username=\"%s\", "
512                "realm=\"%s\", "
513                "nonce=\"%s\", "
514                "uri=\"%s\", "
515                "cnonce=\"%s\", "
516                "nc=%08x, "
517                "qop=%s, "
518                "response=\"%s\"",
519                proxy?"Proxy-":"",
520                userp_quoted,
521                d->realm,
522                d->nonce,
523                uripath, /* this is the PATH part of the URL */
524                d->cnonce,
525                d->nc,
526                d->qop,
527                request_digest);
528
529     if(Curl_raw_equal(d->qop, "auth"))
530       d->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0 padded
531                   which tells to the server how many times you are using the
532                   same nonce in the qop=auth mode. */
533   }
534   else {
535     *allocuserpwd =
536       aprintf( "%sAuthorization: Digest "
537                "username=\"%s\", "
538                "realm=\"%s\", "
539                "nonce=\"%s\", "
540                "uri=\"%s\", "
541                "response=\"%s\"",
542                proxy?"Proxy-":"",
543                userp_quoted,
544                d->realm,
545                d->nonce,
546                uripath, /* this is the PATH part of the URL */
547                request_digest);
548   }
549   Curl_safefree(userp_quoted);
550   if(!*allocuserpwd)
551     return CURLE_OUT_OF_MEMORY;
552
553   /* Add optional fields */
554   if(d->opaque) {
555     /* append opaque */
556     tmp = aprintf("%s, opaque=\"%s\"", *allocuserpwd, d->opaque);
557     if(!tmp)
558       return CURLE_OUT_OF_MEMORY;
559     free(*allocuserpwd);
560     *allocuserpwd = tmp;
561   }
562
563   if(d->algorithm) {
564     /* append algorithm */
565     tmp = aprintf("%s, algorithm=\"%s\"", *allocuserpwd, d->algorithm);
566     if(!tmp)
567       return CURLE_OUT_OF_MEMORY;
568     free(*allocuserpwd);
569     *allocuserpwd = tmp;
570   }
571
572   /* append CRLF + zero (3 bytes) to the userpwd header */
573   userlen = strlen(*allocuserpwd);
574   tmp = realloc(*allocuserpwd, userlen + 3);
575   if(!tmp)
576     return CURLE_OUT_OF_MEMORY;
577   strcpy(&tmp[userlen], "\r\n"); /* append the data */
578   *allocuserpwd = tmp;
579
580   return CURLE_OK;
581 }
582
583 static void digest_cleanup_one(struct digestdata *d)
584 {
585   Curl_safefree(d->nonce);
586   Curl_safefree(d->cnonce);
587   Curl_safefree(d->realm);
588   Curl_safefree(d->opaque);
589   Curl_safefree(d->qop);
590   Curl_safefree(d->algorithm);
591
592   d->nc = 0;
593   d->algo = CURLDIGESTALGO_MD5; /* default algorithm */
594   d->stale = FALSE; /* default means normal, not stale */
595 }
596
597
598 void Curl_digest_cleanup(struct SessionHandle *data)
599 {
600   digest_cleanup_one(&data->state.digest);
601   digest_cleanup_one(&data->state.proxydigest);
602 }
603
604 #endif