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