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 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 */
19 
27 #include "platform.h"
28 #include "internal.h"
29 #include "md5.h"
30 #include "base64.h"
31 
32 #define HASH_MD5_HEX_LEN (2 * MD5_DIGEST_SIZE)
33 
37 #define _BASE "Digest "
38 
42 #define _BASIC_BASE "Basic "
43 
47 #define MAX_USERNAME_LENGTH 128
48 
52 #define MAX_REALM_LENGTH 256
53 
57 #define MAX_AUTH_RESPONSE_LENGTH 128
58 
66 static void
67 cvthex(const unsigned char *bin,
68  size_t len,
69  char *hex)
70 {
71  size_t i;
72  unsigned int j;
73 
74  for (i = 0; i < len; ++i)
75  {
76  j = (bin[i] >> 4) & 0x0f;
77  hex[i * 2] = j <= 9 ? (j + '0') : (j + 'a' - 10);
78  j = bin[i] & 0x0f;
79  hex[i * 2 + 1] = j <= 9 ? (j + '0') : (j + 'a' - 10);
80  }
81  hex[len * 2] = '\0';
82 }
83 
96 static void
97 digest_calc_ha1(const char *alg,
98  const char *username,
99  const char *realm,
100  const char *password,
101  const char *nonce,
102  const char *cnonce,
103  char *sessionkey)
104 {
105  struct MD5Context md5;
106  unsigned char ha1[MD5_DIGEST_SIZE];
107 
108  MD5Init (&md5);
109  MD5Update (&md5, username, strlen (username));
110  MD5Update (&md5, ":", 1);
111  MD5Update (&md5, realm, strlen (realm));
112  MD5Update (&md5, ":", 1);
113  MD5Update (&md5, password, strlen (password));
114  MD5Final (ha1, &md5);
115  if (0 == strcasecmp(alg, "md5-sess"))
116  {
117  MD5Init (&md5);
118  MD5Update (&md5, ha1, sizeof (ha1));
119  MD5Update (&md5, ":", 1);
120  MD5Update (&md5, nonce, strlen (nonce));
121  MD5Update (&md5, ":", 1);
122  MD5Update (&md5, cnonce, strlen (cnonce));
123  MD5Final (ha1, &md5);
124  }
125  cvthex(ha1, sizeof (ha1), sessionkey);
126 }
127 
128 
142 static void
143 digest_calc_response(const char *ha1,
144  const char *nonce,
145  const char *noncecount,
146  const char *cnonce,
147  const char *qop,
148  const char *method,
149  const char *uri,
150  const char *hentity,
151  char *response)
152 {
153  struct MD5Context md5;
154  unsigned char ha2[MD5_DIGEST_SIZE];
155  unsigned char resphash[MD5_DIGEST_SIZE];
156  char ha2hex[HASH_MD5_HEX_LEN + 1];
157 
158  MD5Init (&md5);
159  MD5Update (&md5, method, strlen(method));
160  MD5Update (&md5, ":", 1);
161  MD5Update (&md5, uri, strlen(uri));
162 #if 0
163  if (strcasecmp(qop, "auth-int") == 0)
164  {
165  /* This is dead code since the rest of this module does
166  not support auth-int. */
167  MD5Update (&md5, ":", 1);
168  if (hentity != NULL)
169  MD5Update (&md5, hentity, strlen(hentity));
170  }
171 #endif
172  MD5Final (ha2, &md5);
173  cvthex(ha2, MD5_DIGEST_SIZE, ha2hex);
174  MD5Init (&md5);
175  /* calculate response */
176  MD5Update (&md5, ha1, HASH_MD5_HEX_LEN);
177  MD5Update (&md5, ":", 1);
178  MD5Update (&md5, nonce, strlen(nonce));
179  MD5Update (&md5, ":", 1);
180  if ('\0' != *qop)
181  {
182  MD5Update (&md5, noncecount, strlen(noncecount));
183  MD5Update (&md5, ":", 1);
184  MD5Update (&md5, cnonce, strlen(cnonce));
185  MD5Update (&md5, ":", 1);
186  MD5Update (&md5, qop, strlen(qop));
187  MD5Update (&md5, ":", 1);
188  }
189  MD5Update (&md5, ha2hex, HASH_MD5_HEX_LEN);
190  MD5Final (resphash, &md5);
191  cvthex(resphash, sizeof (resphash), response);
192 }
193 
194 
209 static int
210 lookup_sub_value(char *dest,
211  size_t size,
212  const char *data,
213  const char *key)
214 {
215  size_t keylen = strlen(key);
216  size_t len;
217  const char *ptr = data;
218  const char *eq;
219  const char *q1;
220  const char *q2;
221  const char *qn;
222 
223  if (0 == size)
224  return 0;
225  while ('\0' != *ptr)
226  {
227  if (NULL == (eq = strstr (ptr, "=")))
228  return 0;
229  q1 = eq + 1;
230  while (' ' == *q1)
231  q1++;
232  if ('\"' != *q1)
233  {
234  q2 = strstr (q1, ",");
235  qn = q2;
236  }
237  else
238  {
239  q1++;
240  q2 = strstr (q1, "\"");
241  if (NULL == q2)
242  return 0; /* end quote not found */
243  qn = q2 + 1;
244  }
245  if ( (0 == strncasecmp (ptr,
246  key,
247  keylen)) &&
248  (eq == &ptr[keylen]) )
249  {
250  if (q2 == NULL)
251  {
252  len = strlen (q1) + 1;
253  if (size > len)
254  size = len;
255  size--;
256  strncpy (dest,
257  q1,
258  size);
259  dest[size] = '\0';
260  return size;
261  }
262  else
263  {
264  if (size > (q2 - q1) + 1)
265  size = (q2 - q1) + 1;
266  size--;
267  memcpy (dest,
268  q1,
269  size);
270  dest[size] = '\0';
271  return size;
272  }
273  }
274  if (NULL == qn)
275  return 0;
276  ptr = strstr (qn, ",");
277  if (NULL == ptr)
278  return 0;
279  ptr++;
280  while (' ' == *ptr)
281  ptr++;
282  }
283  return 0;
284 }
285 
286 
296 static int
297 check_nonce_nc (struct MHD_Connection *connection,
298  const char *nonce,
299  unsigned int nc)
300 {
301  uint32_t off;
302  uint32_t mod;
303  const char *np;
304 
305  mod = connection->daemon->nonce_nc_size;
306  if (0 == mod)
307  return MHD_NO; /* no array! */
308  /* super-fast xor-based "hash" function for HT lookup in nonce array */
309  off = 0;
310  np = nonce;
311  while (*np != '\0')
312  {
313  off = (off << 8) | (*np ^ (off >> 24));
314  np++;
315  }
316  off = off % mod;
317  /*
318  * Look for the nonce, if it does exist and its corresponding
319  * nonce counter is less than the current nonce counter by 1,
320  * then only increase the nonce counter by one.
321  */
322 
323  pthread_mutex_lock(&connection->daemon->nnc_lock);
324  if (nc == 0)
325  {
326  strcpy(connection->daemon->nnc[off].nonce,
327  nonce);
328  connection->daemon->nnc[off].nc = 0;
329  pthread_mutex_unlock(&connection->daemon->nnc_lock);
330  return MHD_YES;
331  }
332  if ( (nc <= connection->daemon->nnc[off].nc) ||
333  (0 != strcmp(connection->daemon->nnc[off].nonce, nonce)) )
334  {
335  pthread_mutex_unlock(&connection->daemon->nnc_lock);
336 #if HAVE_MESSAGES
337  MHD_DLOG (connection->daemon,
338  "Stale nonce received. If this happens a lot, you should probably increase the size of the nonce array.\n");
339 #endif
340  return MHD_NO;
341  }
342  connection->daemon->nnc[off].nc = nc;
343  pthread_mutex_unlock(&connection->daemon->nnc_lock);
344  return MHD_YES;
345 }
346 
347 
355 char *
357 {
358  size_t len;
359  char user[MAX_USERNAME_LENGTH];
360  const char *header;
361 
362  header = MHD_lookup_connection_value(connection,
365  if (header == NULL)
366  return NULL;
367  if (strncmp(header, _BASE, strlen(_BASE)) != 0)
368  return NULL;
369  header += strlen (_BASE);
370  len = lookup_sub_value(user,
371  sizeof (user),
372  header,
373  "username");
374  if (!len)
375  return NULL;
376  return strdup(user);
377 }
378 
379 
393 static void
394 calculate_nonce (uint32_t nonce_time,
395  const char *method,
396  const char *rnd,
397  unsigned int rnd_size,
398  const char *uri,
399  const char *realm,
400  char *nonce)
401 {
402  struct MD5Context md5;
403  unsigned char timestamp[4];
404  unsigned char tmpnonce[MD5_DIGEST_SIZE];
405  char timestamphex[sizeof(timestamp)*2+1];
406 
407  MD5Init (&md5);
408  timestamp[0] = (nonce_time & 0xff000000) >> 0x18;
409  timestamp[1] = (nonce_time & 0x00ff0000) >> 0x10;
410  timestamp[2] = (nonce_time & 0x0000ff00) >> 0x08;
411  timestamp[3] = (nonce_time & 0x000000ff);
412  MD5Update(&md5, timestamp, 4);
413  MD5Update(&md5, ":", 1);
414  MD5Update(&md5, method, strlen(method));
415  MD5Update(&md5, ":", 1);
416  if (rnd_size > 0)
417  MD5Update(&md5, rnd, rnd_size);
418  MD5Update(&md5, ":", 1);
419  MD5Update(&md5, uri, strlen(uri));
420  MD5Update(&md5, ":", 1);
421  MD5Update(&md5, realm, strlen(realm));
422  MD5Final (tmpnonce, &md5);
423  cvthex(tmpnonce, sizeof (tmpnonce), nonce);
424  cvthex(timestamp, 4, timestamphex);
425  strncat(nonce, timestamphex, 8);
426 }
427 
428 
441 int
443  const char *realm,
444  const char *username,
445  const char *password,
446  unsigned int nonce_timeout)
447 {
448  size_t len;
449  const char *header;
450  char nonce[MAX_NONCE_LENGTH];
451  char cnonce[MAX_NONCE_LENGTH];
452  char qop[15]; /* auth,auth-int */
453  char nc[20];
454  char response[MAX_AUTH_RESPONSE_LENGTH];
455  const char *hentity = NULL; /* "auth-int" is not supported */
456  char ha1[HASH_MD5_HEX_LEN + 1];
457  char respexp[HASH_MD5_HEX_LEN + 1];
458  char noncehashexp[HASH_MD5_HEX_LEN + 9];
459  uint32_t nonce_time;
460  uint32_t t;
461  size_t left; /* number of characters left in 'header' for 'uri' */
462  unsigned int nci;
463 
464  header = MHD_lookup_connection_value(connection,
467  if (header == NULL)
468  return MHD_NO;
469  if (strncmp(header, _BASE, strlen(_BASE)) != 0)
470  return MHD_NO;
471  header += strlen (_BASE);
472  left = strlen (header);
473 
474  {
475  char un[MAX_USERNAME_LENGTH];
476  len = lookup_sub_value(un,
477  sizeof (un),
478  header, "username");
479  if ( (!len) ||
480  (strcmp(username, un) != 0) )
481  return MHD_NO;
482  left -= strlen ("username") + len;
483  }
484 
485  {
486  char r[MAX_REALM_LENGTH];
487  len = lookup_sub_value(r,
488  sizeof (r),
489  header, "realm");
490  if ( (!len) ||
491  (strcmp(realm, r) != 0) )
492  return MHD_NO;
493  left -= strlen ("realm") + len;
494  }
495 
496  if (0 == (len = lookup_sub_value(nonce,
497  sizeof (nonce),
498  header, "nonce")))
499  return MHD_NO;
500  left -= strlen ("nonce") + len;
501 
502  {
503  char uri[left];
504 
505  if (0 == lookup_sub_value(uri,
506  sizeof (uri),
507  header, "uri"))
508  return MHD_NO;
509 
510  /* 8 = 4 hexadecimal numbers for the timestamp */
511  nonce_time = strtoul(nonce + len - 8, (char **)NULL, 16);
512  t = (uint32_t) time(NULL);
513  /*
514  * First level vetting for the nonce validity
515  * if the timestamp attached to the nonce
516  * exceeds `nonce_timeout' then the nonce is
517  * invalid.
518  */
519  if (t > nonce_time + nonce_timeout)
520  return MHD_INVALID_NONCE;
521  calculate_nonce (nonce_time,
522  connection->method,
523  connection->daemon->digest_auth_random,
524  connection->daemon->digest_auth_rand_size,
525  uri,
526  realm,
527  noncehashexp);
528  /*
529  * Second level vetting for the nonce validity
530  * if the timestamp attached to the nonce is valid
531  * and possibly fabricated (in case of an attack)
532  * the attacker must also know the random seed to be
533  * able to generate a "sane" nonce, which if he does
534  * not, the nonce fabrication process going to be
535  * very hard to achieve.
536  */
537 
538  if (0 != strcmp(nonce, noncehashexp))
539  return MHD_INVALID_NONCE;
540  if ( (0 == lookup_sub_value(cnonce,
541  sizeof (cnonce),
542  header, "cnonce")) ||
543  (0 == lookup_sub_value(qop, sizeof (qop), header, "qop")) ||
544  ( (0 != strcmp (qop, "auth")) &&
545  (0 != strcmp (qop, "")) ) ||
546  (0 == lookup_sub_value(nc, sizeof (nc), header, "nc")) ||
547  (1 != sscanf (nc, "%u", &nci)) ||
548  (0 == lookup_sub_value(response, sizeof (response), header, "response")) )
549  return MHD_NO;
550 
551  /*
552  * Checking if that combination of nonce and nc is sound
553  * and not a replay attack attempt. Also adds the nonce
554  * to the nonce-nc map if it does not exist there.
555  */
556 
557  if (MHD_YES != check_nonce_nc (connection, nonce, nci))
558  return MHD_NO;
559 
560  digest_calc_ha1("md5",
561  username,
562  realm,
563  password,
564  nonce,
565  cnonce,
566  ha1);
568  nonce,
569  nc,
570  cnonce,
571  qop,
572  connection->method,
573  uri,
574  hentity,
575  respexp);
576  return strcmp(response, respexp) == 0 ? MHD_YES : MHD_NO;
577  }
578 }
579 
580 
591 int
593  const char *realm,
594  const char *opaque,
595  struct MHD_Response *response,
596  int signal_stale)
597 {
598  int ret;
599  size_t hlen;
600  char nonce[HASH_MD5_HEX_LEN + 9];
601 
602  /* Generating the server nonce */
603  calculate_nonce ((uint32_t) time(NULL),
604  connection->method,
605  connection->daemon->digest_auth_random,
606  connection->daemon->digest_auth_rand_size,
607  connection->url,
608  realm,
609  nonce);
610  if (MHD_YES != check_nonce_nc (connection, nonce, 0))
611  {
612 #if HAVE_MESSAGES
613  MHD_DLOG (connection->daemon,
614  "Could not register nonce (is the nonce array size zero?).\n");
615 #endif
616  return MHD_NO;
617  }
618  /* Building the authentication header */
619  hlen = snprintf(NULL,
620  0,
621  "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s",
622  realm,
623  nonce,
624  opaque,
625  signal_stale ? ",stale=\"true\"" : "");
626  {
627  char header[hlen + 1];
628  snprintf(header,
629  sizeof(header),
630  "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s",
631  realm,
632  nonce,
633  opaque,
634  signal_stale ? ",stale=\"true\"" : "");
635  ret = MHD_add_response_header(response,
637  header);
638  }
639  if (MHD_YES == ret)
640  ret = MHD_queue_response(connection,
642  response);
643  return ret;
644 }
645 
646 
655 char *
657  char** password)
658 {
659  const char *header;
660  char *decode;
661  const char *separator;
662  char *user;
663 
664  header = MHD_lookup_connection_value(connection,
667  if (header == NULL)
668  return NULL;
669  if (strncmp(header, _BASIC_BASE, strlen(_BASIC_BASE)) != 0)
670  return NULL;
671  header += strlen(_BASIC_BASE);
672  decode = BASE64Decode(header);
673  if (decode == NULL)
674  {
675 #if HAVE_MESSAGES
676  MHD_DLOG(connection->daemon,
677  "Error decoding basic authentication\n");
678 #endif
679  return NULL;
680  }
681  /* Find user:password pattern */
682  separator = strstr(decode, ":");
683  if (separator == NULL)
684  {
685 #if HAVE_MESSAGES
686  MHD_DLOG(connection->daemon,
687  "Basic authentication doesn't contain ':' separator\n");
688 #endif
689  free(decode);
690  return NULL;
691  }
692  user = strdup(decode);
693  if (NULL == user)
694  {
695  free (decode);
696  return NULL;
697  }
698  user[separator - decode] = '\0'; /* cut off at ':' */
699  if (password != NULL)
700  {
701  *password = strdup(separator + 1);
702  if (NULL == *password)
703  {
704 #if HAVE_MESSAGES
705  MHD_DLOG(connection->daemon,
706  "Failed to allocate memory for password\n");
707 #endif
708  free (decode);
709  free (user);
710  return NULL;
711  }
712  }
713  free(decode);
714  return user;
715 }
716 
717 
725 int
727  const char *realm,
728  struct MHD_Response *response)
729 {
730  int ret;
731  size_t hlen = strlen(realm) + strlen("Basic realm=\"\"") + 1;
732  char header[hlen];
733 
734  snprintf(header,
735  sizeof (header),
736  "Basic realm=\"%s\"",
737  realm);
738  ret = MHD_add_response_header(response,
740  header);
741  if (MHD_YES == ret)
742  ret = MHD_queue_response(connection,
744  response);
745  return ret;
746 }
747 
748 /* end of digestauth.c */