GNU libmicrohttpd  0.9.5
digestauth.c
Go to the documentation of this file.
1 /*
2  This file is part of libmicrohttpd
3  (C) 2010, 2011, 2012 Daniel Pittman and Christian Grothoff
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Lesser General Public
7  License as published by the Free Software Foundation; either
8  version 2.1 of the License, or (at your option) any later version.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Lesser General Public License for more details.
14 
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, write to the Free Software
17  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
25 #include "platform.h"
26 #include <limits.h>
27 #include "internal.h"
28 #include "md5.h"
29 
30 #define HASH_MD5_HEX_LEN (2 * MD5_DIGEST_SIZE)
31 
35 #define _BASE "Digest "
36 
40 #define MAX_USERNAME_LENGTH 128
41 
45 #define MAX_REALM_LENGTH 256
46 
50 #define MAX_AUTH_RESPONSE_LENGTH 128
51 
52 
60 static void
61 cvthex (const unsigned char *bin,
62  size_t len,
63  char *hex)
64 {
65  size_t i;
66  unsigned int j;
67 
68  for (i = 0; i < len; ++i)
69  {
70  j = (bin[i] >> 4) & 0x0f;
71  hex[i * 2] = j <= 9 ? (j + '0') : (j + 'a' - 10);
72  j = bin[i] & 0x0f;
73  hex[i * 2 + 1] = j <= 9 ? (j + '0') : (j + 'a' - 10);
74  }
75  hex[len * 2] = '\0';
76 }
77 
78 
91 static void
92 digest_calc_ha1 (const char *alg,
93  const char *username,
94  const char *realm,
95  const char *password,
96  const char *nonce,
97  const char *cnonce,
98  char *sessionkey)
99 {
100  struct MD5Context md5;
101  unsigned char ha1[MD5_DIGEST_SIZE];
102 
103  MD5Init (&md5);
104  MD5Update (&md5, username, strlen (username));
105  MD5Update (&md5, ":", 1);
106  MD5Update (&md5, realm, strlen (realm));
107  MD5Update (&md5, ":", 1);
108  MD5Update (&md5, password, strlen (password));
109  MD5Final (ha1, &md5);
110  if (0 == strcasecmp (alg, "md5-sess"))
111  {
112  MD5Init (&md5);
113  MD5Update (&md5, ha1, sizeof (ha1));
114  MD5Update (&md5, ":", 1);
115  MD5Update (&md5, nonce, strlen (nonce));
116  MD5Update (&md5, ":", 1);
117  MD5Update (&md5, cnonce, strlen (cnonce));
118  MD5Final (ha1, &md5);
119  }
120  cvthex (ha1, sizeof (ha1), sessionkey);
121 }
122 
123 
137 static void
138 digest_calc_response (const char *ha1,
139  const char *nonce,
140  const char *noncecount,
141  const char *cnonce,
142  const char *qop,
143  const char *method,
144  const char *uri,
145  const char *hentity,
146  char *response)
147 {
148  struct MD5Context md5;
149  unsigned char ha2[MD5_DIGEST_SIZE];
150  unsigned char resphash[MD5_DIGEST_SIZE];
151  char ha2hex[HASH_MD5_HEX_LEN + 1];
152 
153  MD5Init (&md5);
154  MD5Update (&md5, method, strlen(method));
155  MD5Update (&md5, ":", 1);
156  MD5Update (&md5, uri, strlen(uri));
157 #if 0
158  if (0 == strcasecmp(qop, "auth-int"))
159  {
160  /* This is dead code since the rest of this module does
161  not support auth-int. */
162  MD5Update (&md5, ":", 1);
163  if (NULL != hentity)
164  MD5Update (&md5, hentity, strlen(hentity));
165  }
166 #endif
167  MD5Final (ha2, &md5);
168  cvthex (ha2, MD5_DIGEST_SIZE, ha2hex);
169  MD5Init (&md5);
170  /* calculate response */
171  MD5Update (&md5, ha1, HASH_MD5_HEX_LEN);
172  MD5Update (&md5, ":", 1);
173  MD5Update (&md5, nonce, strlen(nonce));
174  MD5Update (&md5, ":", 1);
175  if ('\0' != *qop)
176  {
177  MD5Update (&md5, noncecount, strlen(noncecount));
178  MD5Update (&md5, ":", 1);
179  MD5Update (&md5, cnonce, strlen(cnonce));
180  MD5Update (&md5, ":", 1);
181  MD5Update (&md5, qop, strlen(qop));
182  MD5Update (&md5, ":", 1);
183  }
184  MD5Update (&md5, ha2hex, HASH_MD5_HEX_LEN);
185  MD5Final (resphash, &md5);
186  cvthex (resphash, sizeof (resphash), response);
187 }
188 
189 
204 static int
205 lookup_sub_value (char *dest,
206  size_t size,
207  const char *data,
208  const char *key)
209 {
210  size_t keylen;
211  size_t len;
212  const char *ptr;
213  const char *eq;
214  const char *q1;
215  const char *q2;
216  const char *qn;
217 
218  if (0 == size)
219  return 0;
220  keylen = strlen (key);
221  ptr = data;
222  while ('\0' != *ptr)
223  {
224  if (NULL == (eq = strchr (ptr, '=')))
225  return 0;
226  q1 = eq + 1;
227  while (' ' == *q1)
228  q1++;
229  if ('\"' != *q1)
230  {
231  q2 = strchr (q1, ',');
232  qn = q2;
233  }
234  else
235  {
236  q1++;
237  q2 = strchr (q1, '\"');
238  if (NULL == q2)
239  return 0; /* end quote not found */
240  qn = q2 + 1;
241  }
242  if ( (0 == strncasecmp (ptr,
243  key,
244  keylen)) &&
245  (eq == &ptr[keylen]) )
246  {
247  if (NULL == q2)
248  {
249  len = strlen (q1) + 1;
250  if (size > len)
251  size = len;
252  size--;
253  strncpy (dest,
254  q1,
255  size);
256  dest[size] = '\0';
257  return size;
258  }
259  else
260  {
261  if (size > (q2 - q1) + 1)
262  size = (q2 - q1) + 1;
263  size--;
264  memcpy (dest,
265  q1,
266  size);
267  dest[size] = '\0';
268  return size;
269  }
270  }
271  if (NULL == qn)
272  return 0;
273  ptr = strchr (qn, ',');
274  if (NULL == ptr)
275  return 0;
276  ptr++;
277  while (' ' == *ptr)
278  ptr++;
279  }
280  return 0;
281 }
282 
283 
293 static int
294 check_nonce_nc (struct MHD_Connection *connection,
295  const char *nonce,
296  unsigned long int nc)
297 {
298  uint32_t off;
299  uint32_t mod;
300  const char *np;
301 
302  mod = connection->daemon->nonce_nc_size;
303  if (0 == mod)
304  return MHD_NO; /* no array! */
305  /* super-fast xor-based "hash" function for HT lookup in nonce array */
306  off = 0;
307  np = nonce;
308  while ('\0' != *np)
309  {
310  off = (off << 8) | (*np ^ (off >> 24));
311  np++;
312  }
313  off = off % mod;
314  /*
315  * Look for the nonce, if it does exist and its corresponding
316  * nonce counter is less than the current nonce counter by 1,
317  * then only increase the nonce counter by one.
318  */
319 
320  pthread_mutex_lock (&connection->daemon->nnc_lock);
321  if (0 == nc)
322  {
323  strcpy(connection->daemon->nnc[off].nonce,
324  nonce);
325  connection->daemon->nnc[off].nc = 0;
326  pthread_mutex_unlock (&connection->daemon->nnc_lock);
327  return MHD_YES;
328  }
329  if ( (nc <= connection->daemon->nnc[off].nc) ||
330  (0 != strcmp(connection->daemon->nnc[off].nonce, nonce)) )
331  {
332  pthread_mutex_unlock (&connection->daemon->nnc_lock);
333 #if HAVE_MESSAGES
334  MHD_DLOG (connection->daemon,
335  "Stale nonce received. If this happens a lot, you should probably increase the size of the nonce array.\n");
336 #endif
337  return MHD_NO;
338  }
339  connection->daemon->nnc[off].nc = nc;
340  pthread_mutex_unlock (&connection->daemon->nnc_lock);
341  return MHD_YES;
342 }
343 
344 
352 char *
354 {
355  size_t len;
356  char user[MAX_USERNAME_LENGTH];
357  const char *header;
358 
359  if (NULL == (header = MHD_lookup_connection_value (connection,
362  return NULL;
363  if (0 != strncmp (header, _BASE, strlen (_BASE)))
364  return NULL;
365  header += strlen (_BASE);
366  if (0 == (len = lookup_sub_value (user,
367  sizeof (user),
368  header,
369  "username")))
370  return NULL;
371  return strdup (user);
372 }
373 
374 
388 static void
389 calculate_nonce (uint32_t nonce_time,
390  const char *method,
391  const char *rnd,
392  unsigned int rnd_size,
393  const char *uri,
394  const char *realm,
395  char *nonce)
396 {
397  struct MD5Context md5;
398  unsigned char timestamp[4];
399  unsigned char tmpnonce[MD5_DIGEST_SIZE];
400  char timestamphex[sizeof(timestamp) * 2 + 1];
401 
402  MD5Init (&md5);
403  timestamp[0] = (nonce_time & 0xff000000) >> 0x18;
404  timestamp[1] = (nonce_time & 0x00ff0000) >> 0x10;
405  timestamp[2] = (nonce_time & 0x0000ff00) >> 0x08;
406  timestamp[3] = (nonce_time & 0x000000ff);
407  MD5Update (&md5, timestamp, 4);
408  MD5Update (&md5, ":", 1);
409  MD5Update (&md5, method, strlen(method));
410  MD5Update (&md5, ":", 1);
411  if (rnd_size > 0)
412  MD5Update (&md5, rnd, rnd_size);
413  MD5Update (&md5, ":", 1);
414  MD5Update (&md5, uri, strlen(uri));
415  MD5Update (&md5, ":", 1);
416  MD5Update (&md5, realm, strlen(realm));
417  MD5Final (tmpnonce, &md5);
418  cvthex (tmpnonce, sizeof (tmpnonce), nonce);
419  cvthex (timestamp, 4, timestamphex);
420  strncat (nonce, timestamphex, 8);
421 }
422 
423 
434 static int
435 test_header (struct MHD_Connection *connection,
436  const char *key,
437  const char *value)
438 {
439  struct MHD_HTTP_Header *pos;
440 
441  for (pos = connection->headers_received; NULL != pos; pos = pos->next)
442  {
443  if (MHD_GET_ARGUMENT_KIND != pos->kind)
444  continue;
445  if (0 != strcmp (key, pos->header))
446  continue;
447  if ( (NULL == value) &&
448  (NULL == pos->value) )
449  return MHD_YES;
450  if ( (NULL == value) ||
451  (NULL == pos->value) ||
452  (0 != strcmp (value, pos->value)) )
453  continue;
454  return MHD_YES;
455  }
456  return MHD_NO;
457 }
458 
459 
470 static int
472  const char *args)
473 {
474  struct MHD_HTTP_Header *pos;
475  size_t slen = strlen (args) + 1;
476  char argb[slen];
477  char *argp;
478  char *equals;
479  char *amper;
480  unsigned int num_headers;
481 
482  num_headers = 0;
483  memcpy (argb, args, slen);
484  argp = argb;
485  while ( (NULL != argp) &&
486  ('\0' != argp[0]) )
487  {
488  equals = strchr (argp, '=');
489  if (NULL == equals)
490  {
491  /* add with 'value' NULL */
492  connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
493  connection,
494  argp);
495  if (MHD_YES != test_header (connection, argp, NULL))
496  return MHD_NO;
497  num_headers++;
498  break;
499  }
500  equals[0] = '\0';
501  equals++;
502  amper = strchr (equals, '&');
503  if (NULL != amper)
504  {
505  amper[0] = '\0';
506  amper++;
507  }
508  connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
509  connection,
510  argp);
511  connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
512  connection,
513  equals);
514  if (! test_header (connection, argp, equals))
515  return MHD_NO;
516  num_headers++;
517  argp = amper;
518  }
519 
520  /* also check that the number of headers matches */
521  for (pos = connection->headers_received; NULL != pos; pos = pos->next)
522  {
523  if (MHD_GET_ARGUMENT_KIND != pos->kind)
524  continue;
525  num_headers--;
526  }
527  if (0 != num_headers)
528  return MHD_NO;
529  return MHD_YES;
530 }
531 
532 
545 int
547  const char *realm,
548  const char *username,
549  const char *password,
550  unsigned int nonce_timeout)
551 {
552  size_t len;
553  const char *header;
554  char *end;
555  char nonce[MAX_NONCE_LENGTH];
556  char cnonce[MAX_NONCE_LENGTH];
557  char qop[15]; /* auth,auth-int */
558  char nc[20];
559  char response[MAX_AUTH_RESPONSE_LENGTH];
560  const char *hentity = NULL; /* "auth-int" is not supported */
561  char ha1[HASH_MD5_HEX_LEN + 1];
562  char respexp[HASH_MD5_HEX_LEN + 1];
563  char noncehashexp[HASH_MD5_HEX_LEN + 9];
564  uint32_t nonce_time;
565  uint32_t t;
566  size_t left; /* number of characters left in 'header' for 'uri' */
567  unsigned long int nci;
568 
569  header = MHD_lookup_connection_value (connection,
572  if (NULL == header)
573  return MHD_NO;
574  if (0 != strncmp(header, _BASE, strlen(_BASE)))
575  return MHD_NO;
576  header += strlen (_BASE);
577  left = strlen (header);
578 
579  {
580  char un[MAX_USERNAME_LENGTH];
581 
582  len = lookup_sub_value (un,
583  sizeof (un),
584  header, "username");
585  if ( (0 == len) ||
586  (0 != strcmp(username, un)) )
587  return MHD_NO;
588  left -= strlen ("username") + len;
589  }
590 
591  {
592  char r[MAX_REALM_LENGTH];
593 
594  len = lookup_sub_value(r,
595  sizeof (r),
596  header, "realm");
597  if ( (0 == len) ||
598  (0 != strcmp(realm, r)) )
599  return MHD_NO;
600  left -= strlen ("realm") + len;
601  }
602 
603  if (0 == (len = lookup_sub_value (nonce,
604  sizeof (nonce),
605  header, "nonce")))
606  return MHD_NO;
607  left -= strlen ("nonce") + len;
608 
609  {
610  char uri[left];
611 
612  if (0 == lookup_sub_value(uri,
613  sizeof (uri),
614  header, "uri"))
615  return MHD_NO;
616 
617  /* 8 = 4 hexadecimal numbers for the timestamp */
618  nonce_time = strtoul(nonce + len - 8, (char **)NULL, 16);
619  t = (uint32_t) MHD_monotonic_time();
620  /*
621  * First level vetting for the nonce validity if the timestamp
622  * attached to the nonce exceeds `nonce_timeout' then the nonce is
623  * invalid.
624  */
625  if ( (t > nonce_time + nonce_timeout) ||
626  (nonce_time + nonce_timeout < nonce_time) )
627  return MHD_INVALID_NONCE;
628  if (0 != strncmp (uri,
629  connection->url,
630  strlen (connection->url)))
631  {
632 #if HAVE_MESSAGES
633  MHD_DLOG (connection->daemon,
634  "Authentication failed, URI does not match.\n");
635 #endif
636  return MHD_NO;
637  }
638  {
639  const char *args = strchr (uri, '?');
640 
641  if (NULL == args)
642  args = "";
643  else
644  args++;
645  if (MHD_YES !=
646  check_argument_match (connection,
647  args) )
648  {
649 #if HAVE_MESSAGES
650  MHD_DLOG (connection->daemon,
651  "Authentication failed, arguments do not match.\n");
652 #endif
653  return MHD_NO;
654  }
655  }
656  calculate_nonce (nonce_time,
657  connection->method,
658  connection->daemon->digest_auth_random,
659  connection->daemon->digest_auth_rand_size,
660  connection->url,
661  realm,
662  noncehashexp);
663  /*
664  * Second level vetting for the nonce validity
665  * if the timestamp attached to the nonce is valid
666  * and possibly fabricated (in case of an attack)
667  * the attacker must also know the random seed to be
668  * able to generate a "sane" nonce, which if he does
669  * not, the nonce fabrication process going to be
670  * very hard to achieve.
671  */
672 
673  if (0 != strcmp (nonce, noncehashexp))
674  return MHD_INVALID_NONCE;
675  if ( (0 == lookup_sub_value (cnonce,
676  sizeof (cnonce),
677  header, "cnonce")) ||
678  (0 == lookup_sub_value (qop, sizeof (qop), header, "qop")) ||
679  ( (0 != strcmp (qop, "auth")) &&
680  (0 != strcmp (qop, "")) ) ||
681  (0 == lookup_sub_value (nc, sizeof (nc), header, "nc")) ||
682  (0 == lookup_sub_value (response, sizeof (response), header, "response")) )
683  {
684 #if HAVE_MESSAGES
685  MHD_DLOG (connection->daemon,
686  "Authentication failed, invalid format.\n");
687 #endif
688  return MHD_NO;
689  }
690  nci = strtoul (nc, &end, 16);
691  if ( ('\0' != *end) ||
692  ( (LONG_MAX == nci) &&
693  (ERANGE == errno) ) )
694  {
695 #if HAVE_MESSAGES
696  MHD_DLOG (connection->daemon,
697  "Authentication failed, invalid format.\n");
698 #endif
699  return MHD_NO; /* invalid nonce format */
700  }
701  /*
702  * Checking if that combination of nonce and nc is sound
703  * and not a replay attack attempt. Also adds the nonce
704  * to the nonce-nc map if it does not exist there.
705  */
706 
707  if (MHD_YES != check_nonce_nc (connection, nonce, nci))
708  return MHD_NO;
709 
710  digest_calc_ha1("md5",
711  username,
712  realm,
713  password,
714  nonce,
715  cnonce,
716  ha1);
718  nonce,
719  nc,
720  cnonce,
721  qop,
722  connection->method,
723  uri,
724  hentity,
725  respexp);
726  return (0 == strcmp(response, respexp))
727  ? MHD_YES
728  : MHD_NO;
729  }
730 }
731 
732 
743 int
745  const char *realm,
746  const char *opaque,
747  struct MHD_Response *response,
748  int signal_stale)
749 {
750  int ret;
751  size_t hlen;
752  char nonce[HASH_MD5_HEX_LEN + 9];
753 
754  /* Generating the server nonce */
755  calculate_nonce ((uint32_t) MHD_monotonic_time(),
756  connection->method,
757  connection->daemon->digest_auth_random,
758  connection->daemon->digest_auth_rand_size,
759  connection->url,
760  realm,
761  nonce);
762  if (MHD_YES != check_nonce_nc (connection, nonce, 0))
763  {
764 #if HAVE_MESSAGES
765  MHD_DLOG (connection->daemon,
766  "Could not register nonce (is the nonce array size zero?).\n");
767 #endif
768  return MHD_NO;
769  }
770  /* Building the authentication header */
771  hlen = snprintf (NULL,
772  0,
773  "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s",
774  realm,
775  nonce,
776  opaque,
777  signal_stale
778  ? ",stale=\"true\""
779  : "");
780  {
781  char header[hlen + 1];
782 
783  snprintf (header,
784  sizeof(header),
785  "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s",
786  realm,
787  nonce,
788  opaque,
789  signal_stale
790  ? ",stale=\"true\""
791  : "");
792  ret = MHD_add_response_header(response,
794  header);
795  }
796  if (MHD_YES == ret)
797  ret = MHD_queue_response(connection,
799  response);
800  return ret;
801 }
802 
803 
804 /* end of digestauth.c */