digestauth.c

Go to the documentation of this file.
00001 /*
00002      This file is part of libmicrohttpd
00003      (C) 2010, 2011, 2012 Daniel Pittman and Christian Grothoff
00004 
00005      This library is free software; you can redistribute it and/or
00006      modify it under the terms of the GNU Lesser General Public
00007      License as published by the Free Software Foundation; either
00008      version 2.1 of the License, or (at your option) any later version.
00009 
00010      This library is distributed in the hope that it will be useful,
00011      but WITHOUT ANY WARRANTY; without even the implied warranty of
00012      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013      Lesser General Public License for more details.
00014 
00015      You should have received a copy of the GNU Lesser General Public
00016      License along with this library; if not, write to the Free Software
00017      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00018 */
00025 #include "platform.h"
00026 #include <limits.h>
00027 #include "internal.h"
00028 #include "md5.h"
00029 
00030 #define HASH_MD5_HEX_LEN (2 * MD5_DIGEST_SIZE)
00031 
00035 #define _BASE           "Digest "
00036 
00040 #define MAX_USERNAME_LENGTH 128
00041 
00045 #define MAX_REALM_LENGTH 256
00046 
00050 #define MAX_AUTH_RESPONSE_LENGTH 128
00051 
00052 
00060 static void
00061 cvthex (const unsigned char *bin,
00062         size_t len,
00063         char *hex)
00064 {
00065   size_t i;
00066   unsigned int j;
00067   
00068   for (i = 0; i < len; ++i) 
00069     {
00070       j = (bin[i] >> 4) & 0x0f;      
00071       hex[i * 2] = j <= 9 ? (j + '0') : (j + 'a' - 10);    
00072       j = bin[i] & 0x0f;    
00073       hex[i * 2 + 1] = j <= 9 ? (j + '0') : (j + 'a' - 10);
00074     }
00075   hex[len * 2] = '\0';
00076 }
00077 
00078 
00091 static void
00092 digest_calc_ha1 (const char *alg,
00093                  const char *username,
00094                  const char *realm,
00095                  const char *password,
00096                  const char *nonce,
00097                  const char *cnonce,
00098                  char *sessionkey)
00099 {
00100   struct MD5Context md5;
00101   unsigned char ha1[MD5_DIGEST_SIZE];
00102   
00103   MD5Init (&md5);
00104   MD5Update (&md5, username, strlen (username));
00105   MD5Update (&md5, ":", 1);
00106   MD5Update (&md5, realm, strlen (realm));
00107   MD5Update (&md5, ":", 1);
00108   MD5Update (&md5, password, strlen (password));
00109   MD5Final (ha1, &md5);
00110   if (0 == strcasecmp (alg, "md5-sess")) 
00111     {
00112       MD5Init (&md5);
00113       MD5Update (&md5, ha1, sizeof (ha1));
00114       MD5Update (&md5, ":", 1);
00115       MD5Update (&md5, nonce, strlen (nonce));
00116       MD5Update (&md5, ":", 1);
00117       MD5Update (&md5, cnonce, strlen (cnonce));
00118       MD5Final (ha1, &md5);
00119     }
00120   cvthex (ha1, sizeof (ha1), sessionkey);
00121 }
00122 
00123 
00137 static void
00138 digest_calc_response (const char *ha1,
00139                       const char *nonce,
00140                       const char *noncecount,
00141                       const char *cnonce,
00142                       const char *qop,
00143                       const char *method,
00144                       const char *uri,
00145                       const char *hentity,
00146                       char *response)
00147 {
00148   struct MD5Context md5;
00149   unsigned char ha2[MD5_DIGEST_SIZE];
00150   unsigned char resphash[MD5_DIGEST_SIZE];
00151   char ha2hex[HASH_MD5_HEX_LEN + 1];
00152   
00153   MD5Init (&md5);
00154   MD5Update (&md5, method, strlen(method));
00155   MD5Update (&md5, ":", 1);
00156   MD5Update (&md5, uri, strlen(uri)); 
00157 #if 0
00158   if (0 == strcasecmp(qop, "auth-int"))
00159     {
00160       /* This is dead code since the rest of this module does
00161          not support auth-int. */
00162       MD5Update (&md5, ":", 1);
00163       if (NULL != hentity)
00164         MD5Update (&md5, hentity, strlen(hentity));
00165     }
00166 #endif  
00167   MD5Final (ha2, &md5);
00168   cvthex (ha2, MD5_DIGEST_SIZE, ha2hex);
00169   MD5Init (&md5);  
00170   /* calculate response */  
00171   MD5Update (&md5, ha1, HASH_MD5_HEX_LEN);
00172   MD5Update (&md5, ":", 1);
00173   MD5Update (&md5, nonce, strlen(nonce));
00174   MD5Update (&md5, ":", 1);  
00175   if ('\0' != *qop)
00176     {
00177       MD5Update (&md5, noncecount, strlen(noncecount));
00178       MD5Update (&md5, ":", 1);
00179       MD5Update (&md5, cnonce, strlen(cnonce));
00180       MD5Update (&md5, ":", 1);
00181       MD5Update (&md5, qop, strlen(qop));
00182       MD5Update (&md5, ":", 1);
00183     }  
00184   MD5Update (&md5, ha2hex, HASH_MD5_HEX_LEN);
00185   MD5Final (resphash, &md5);
00186   cvthex (resphash, sizeof (resphash), response);
00187 }
00188 
00189 
00204 static int
00205 lookup_sub_value (char *dest,
00206                   size_t size,
00207                   const char *data,
00208                   const char *key)
00209 {
00210   size_t keylen;
00211   size_t len;
00212   const char *ptr;
00213   const char *eq;
00214   const char *q1;
00215   const char *q2;
00216   const char *qn;
00217 
00218   if (0 == size)
00219     return 0;
00220   keylen = strlen (key);
00221   ptr = data;
00222   while ('\0' != *ptr)
00223     {
00224       if (NULL == (eq = strchr (ptr, '=')))
00225         return 0;
00226       q1 = eq + 1;
00227       while (' ' == *q1)
00228         q1++;      
00229       if ('\"' != *q1)
00230         {
00231           q2 = strchr (q1, ',');
00232           qn = q2;
00233         }
00234       else
00235         {
00236           q1++;
00237           q2 = strchr (q1, '\"');
00238           if (NULL == q2)
00239             return 0; /* end quote not found */
00240           qn = q2 + 1;
00241         }      
00242       if ( (0 == strncasecmp (ptr,
00243                               key,
00244                               keylen)) &&
00245            (eq == &ptr[keylen]) )
00246         {
00247           if (NULL == q2)
00248             {
00249               len = strlen (q1) + 1;
00250               if (size > len)
00251                 size = len;
00252               size--;
00253               strncpy (dest,
00254                        q1,
00255                        size);
00256               dest[size] = '\0';
00257               return size;
00258             }
00259           else
00260             {
00261               if (size > (q2 - q1) + 1)
00262                 size = (q2 - q1) + 1;
00263               size--;
00264               memcpy (dest, 
00265                       q1,
00266                       size);
00267               dest[size] = '\0';
00268               return size;
00269             }
00270         }
00271       if (NULL == qn)
00272         return 0;
00273       ptr = strchr (qn, ',');
00274       if (NULL == ptr)
00275         return 0;
00276       ptr++;
00277       while (' ' == *ptr)
00278         ptr++;
00279     }
00280   return 0;
00281 }
00282 
00283 
00293 static int
00294 check_nonce_nc (struct MHD_Connection *connection,
00295                 const char *nonce,
00296                 unsigned long int nc)
00297 {
00298   uint32_t off;
00299   uint32_t mod;
00300   const char *np;
00301 
00302   mod = connection->daemon->nonce_nc_size;
00303   if (0 == mod)
00304     return MHD_NO; /* no array! */
00305   /* super-fast xor-based "hash" function for HT lookup in nonce array */
00306   off = 0;
00307   np = nonce;
00308   while ('\0' != *np)
00309     {
00310       off = (off << 8) | (*np ^ (off >> 24));
00311       np++;
00312     }
00313   off = off % mod;
00314   /*
00315    * Look for the nonce, if it does exist and its corresponding
00316    * nonce counter is less than the current nonce counter by 1,
00317    * then only increase the nonce counter by one.
00318    */
00319   
00320   pthread_mutex_lock (&connection->daemon->nnc_lock);
00321   if (0 == nc)
00322     {
00323       strcpy(connection->daemon->nnc[off].nonce, 
00324              nonce);
00325       connection->daemon->nnc[off].nc = 0;  
00326       pthread_mutex_unlock (&connection->daemon->nnc_lock);
00327       return MHD_YES;
00328     }
00329   if ( (nc <= connection->daemon->nnc[off].nc) ||
00330        (0 != strcmp(connection->daemon->nnc[off].nonce, nonce)) )
00331     {
00332       pthread_mutex_unlock (&connection->daemon->nnc_lock);
00333 #if HAVE_MESSAGES
00334       MHD_DLOG (connection->daemon, 
00335                 "Stale nonce received.  If this happens a lot, you should probably increase the size of the nonce array.\n");
00336 #endif
00337       return MHD_NO;
00338     }
00339   connection->daemon->nnc[off].nc = nc;
00340   pthread_mutex_unlock (&connection->daemon->nnc_lock);
00341   return MHD_YES;
00342 }
00343 
00344 
00352 char *
00353 MHD_digest_auth_get_username(struct MHD_Connection *connection)
00354 {
00355   size_t len;
00356   char user[MAX_USERNAME_LENGTH];
00357   const char *header;
00358   
00359   if (NULL == (header = MHD_lookup_connection_value (connection,
00360                                                      MHD_HEADER_KIND, 
00361                                                      MHD_HTTP_HEADER_AUTHORIZATION)))
00362     return NULL;
00363   if (0 != strncmp (header, _BASE, strlen (_BASE)))
00364     return NULL;
00365   header += strlen (_BASE);
00366   if (0 == (len = lookup_sub_value (user,
00367                                     sizeof (user),
00368                                     header, 
00369                                     "username")))
00370     return NULL;
00371   return strdup (user);
00372 }
00373 
00374 
00388 static void
00389 calculate_nonce (uint32_t nonce_time,
00390                  const char *method,
00391                  const char *rnd,
00392                  unsigned int rnd_size,
00393                  const char *uri,
00394                  const char *realm,
00395                  char *nonce)
00396 {
00397   struct MD5Context md5;
00398   unsigned char timestamp[4];
00399   unsigned char tmpnonce[MD5_DIGEST_SIZE];
00400   char timestamphex[sizeof(timestamp) * 2 + 1];
00401 
00402   MD5Init (&md5);
00403   timestamp[0] = (nonce_time & 0xff000000) >> 0x18;
00404   timestamp[1] = (nonce_time & 0x00ff0000) >> 0x10;
00405   timestamp[2] = (nonce_time & 0x0000ff00) >> 0x08;
00406   timestamp[3] = (nonce_time & 0x000000ff);    
00407   MD5Update (&md5, timestamp, 4);
00408   MD5Update (&md5, ":", 1);
00409   MD5Update (&md5, method, strlen(method));
00410   MD5Update (&md5, ":", 1);
00411   if (rnd_size > 0)
00412     MD5Update (&md5, rnd, rnd_size);
00413   MD5Update (&md5, ":", 1);
00414   MD5Update (&md5, uri, strlen(uri));
00415   MD5Update (&md5, ":", 1);
00416   MD5Update (&md5, realm, strlen(realm));
00417   MD5Final (tmpnonce, &md5);  
00418   cvthex (tmpnonce, sizeof (tmpnonce), nonce);  
00419   cvthex (timestamp, 4, timestamphex);
00420   strncat (nonce, timestamphex, 8);
00421 }
00422 
00423 
00434 static int
00435 test_header (struct MHD_Connection *connection,
00436              const char *key,
00437              const char *value)
00438 {
00439   struct MHD_HTTP_Header *pos;
00440 
00441   for (pos = connection->headers_received; NULL != pos; pos = pos->next)
00442     {
00443       if (MHD_GET_ARGUMENT_KIND != pos->kind)
00444         continue;
00445       if (0 != strcmp (key, pos->header))
00446         continue;
00447       if ( (NULL == value) && 
00448            (NULL == pos->value) )
00449         return MHD_YES;
00450       if ( (NULL == value) || 
00451            (NULL == pos->value) ||
00452            (0 != strcmp (value, pos->value)) )
00453         continue;
00454       return MHD_YES;      
00455     }
00456   return MHD_NO;
00457 }
00458 
00459 
00470 static int
00471 check_argument_match (struct MHD_Connection *connection,
00472                       const char *args)
00473 {
00474   struct MHD_HTTP_Header *pos;
00475   size_t slen = strlen (args) + 1;
00476   char argb[slen];
00477   char *argp;
00478   char *equals;
00479   char *amper;
00480   unsigned int num_headers;
00481 
00482   num_headers = 0;
00483   memcpy (argb, args, slen);
00484   argp = argb;
00485   while ( (NULL != argp) &&
00486           ('\0' != argp[0]) )
00487     {
00488       equals = strchr (argp, '=');
00489       if (NULL == equals) 
00490         {         
00491           /* add with 'value' NULL */
00492           connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
00493                                                  connection,
00494                                                  argp);
00495           if (MHD_YES != test_header (connection, argp, NULL))
00496             return MHD_NO;
00497           num_headers++;
00498           break;
00499         }
00500       equals[0] = '\0';
00501       equals++;
00502       amper = strchr (equals, '&');
00503       if (NULL != amper)
00504         {
00505           amper[0] = '\0';
00506           amper++;
00507         }
00508       connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
00509                                              connection,
00510                                              argp);
00511       connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
00512                                              connection,
00513                                              equals);
00514       if (! test_header (connection, argp, equals))
00515         return MHD_NO;
00516       num_headers++;
00517       argp = amper;
00518     }
00519   
00520   /* also check that the number of headers matches */
00521   for (pos = connection->headers_received; NULL != pos; pos = pos->next)
00522     {
00523       if (MHD_GET_ARGUMENT_KIND != pos->kind)
00524         continue;
00525       num_headers--;
00526     }
00527   if (0 != num_headers)  
00528     return MHD_NO;
00529   return MHD_YES;
00530 }
00531 
00532 
00545 int
00546 MHD_digest_auth_check (struct MHD_Connection *connection,
00547                        const char *realm,
00548                        const char *username,
00549                        const char *password,
00550                        unsigned int nonce_timeout)
00551 {
00552   size_t len;
00553   const char *header;
00554   char *end;
00555   char nonce[MAX_NONCE_LENGTH];
00556   char cnonce[MAX_NONCE_LENGTH];
00557   char qop[15]; /* auth,auth-int */
00558   char nc[20];
00559   char response[MAX_AUTH_RESPONSE_LENGTH];
00560   const char *hentity = NULL; /* "auth-int" is not supported */
00561   char ha1[HASH_MD5_HEX_LEN + 1];
00562   char respexp[HASH_MD5_HEX_LEN + 1];
00563   char noncehashexp[HASH_MD5_HEX_LEN + 9];
00564   uint32_t nonce_time;
00565   uint32_t t;
00566   size_t left; /* number of characters left in 'header' for 'uri' */
00567   unsigned long int nci;
00568 
00569   header = MHD_lookup_connection_value (connection,
00570                                         MHD_HEADER_KIND,
00571                                         MHD_HTTP_HEADER_AUTHORIZATION);  
00572   if (NULL == header) 
00573     return MHD_NO;
00574   if (0 != strncmp(header, _BASE, strlen(_BASE))) 
00575     return MHD_NO;
00576   header += strlen (_BASE);
00577   left = strlen (header);
00578 
00579   {
00580     char un[MAX_USERNAME_LENGTH];
00581 
00582     len = lookup_sub_value (un,
00583                             sizeof (un),
00584                             header, "username");
00585     if ( (0 == len) ||
00586          (0 != strcmp(username, un)) ) 
00587       return MHD_NO;
00588     left -= strlen ("username") + len;
00589   }
00590 
00591   {
00592     char r[MAX_REALM_LENGTH];
00593 
00594     len = lookup_sub_value(r, 
00595                            sizeof (r),
00596                            header, "realm");  
00597     if ( (0 == len) || 
00598          (0 != strcmp(realm, r)) )
00599       return MHD_NO;
00600     left -= strlen ("realm") + len;
00601   }
00602 
00603   if (0 == (len = lookup_sub_value (nonce, 
00604                                     sizeof (nonce),
00605                                     header, "nonce")))
00606     return MHD_NO;
00607   left -= strlen ("nonce") + len;
00608 
00609   {
00610     char uri[left];  
00611   
00612     if (0 == lookup_sub_value(uri,
00613                               sizeof (uri),
00614                               header, "uri")) 
00615       return MHD_NO;
00616       
00617     /* 8 = 4 hexadecimal numbers for the timestamp */  
00618     nonce_time = strtoul(nonce + len - 8, (char **)NULL, 16);  
00619     t = (uint32_t) MHD_monotonic_time();    
00620     /*
00621      * First level vetting for the nonce validity if the timestamp
00622      * attached to the nonce exceeds `nonce_timeout' then the nonce is
00623      * invalid.
00624      */
00625     if ( (t > nonce_time + nonce_timeout) ||
00626          (nonce_time + nonce_timeout < nonce_time) )
00627       return MHD_INVALID_NONCE;
00628     if (0 != strncmp (uri,
00629                       connection->url,
00630                       strlen (connection->url)))
00631     {
00632 #if HAVE_MESSAGES
00633       MHD_DLOG (connection->daemon, 
00634                 "Authentication failed, URI does not match.\n");
00635 #endif
00636       return MHD_NO;
00637     }
00638     {
00639       const char *args = strchr (uri, '?');
00640 
00641       if (NULL == args)
00642         args = "";
00643       else
00644         args++;
00645       if (MHD_YES !=
00646           check_argument_match (connection,
00647                                 args) ) 
00648       {
00649 #if HAVE_MESSAGES
00650         MHD_DLOG (connection->daemon, 
00651                   "Authentication failed, arguments do not match.\n");
00652 #endif
00653         return MHD_NO;
00654       }
00655     }
00656     calculate_nonce (nonce_time,
00657                      connection->method,
00658                      connection->daemon->digest_auth_random,
00659                      connection->daemon->digest_auth_rand_size,
00660                      connection->url,
00661                      realm,
00662                      noncehashexp);
00663     /*
00664      * Second level vetting for the nonce validity
00665      * if the timestamp attached to the nonce is valid
00666      * and possibly fabricated (in case of an attack)
00667      * the attacker must also know the random seed to be
00668      * able to generate a "sane" nonce, which if he does
00669      * not, the nonce fabrication process going to be
00670      * very hard to achieve.
00671      */
00672     
00673     if (0 != strcmp (nonce, noncehashexp))
00674       return MHD_INVALID_NONCE;
00675     if ( (0 == lookup_sub_value (cnonce,
00676                                  sizeof (cnonce), 
00677                                  header, "cnonce")) ||
00678          (0 == lookup_sub_value (qop, sizeof (qop), header, "qop")) ||
00679          ( (0 != strcmp (qop, "auth")) && 
00680            (0 != strcmp (qop, "")) ) ||
00681          (0 == lookup_sub_value (nc, sizeof (nc), header, "nc"))  ||
00682          (0 == lookup_sub_value (response, sizeof (response), header, "response")) )
00683     {
00684 #if HAVE_MESSAGES
00685       MHD_DLOG (connection->daemon, 
00686                 "Authentication failed, invalid format.\n");
00687 #endif
00688       return MHD_NO;
00689     }
00690     nci = strtoul (nc, &end, 16);
00691     if ( ('\0' != *end) ||
00692          ( (LONG_MAX == nci) && 
00693            (ERANGE == errno) ) )
00694     {
00695 #if HAVE_MESSAGES
00696       MHD_DLOG (connection->daemon, 
00697                 "Authentication failed, invalid format.\n");
00698 #endif
00699       return MHD_NO; /* invalid nonce format */
00700     }
00701     /*
00702      * Checking if that combination of nonce and nc is sound
00703      * and not a replay attack attempt. Also adds the nonce
00704      * to the nonce-nc map if it does not exist there.
00705      */
00706     
00707     if (MHD_YES != check_nonce_nc (connection, nonce, nci))
00708       return MHD_NO;
00709     
00710     digest_calc_ha1("md5",
00711                     username,
00712                     realm,
00713                     password,
00714                     nonce,
00715                     cnonce,
00716                     ha1);
00717     digest_calc_response (ha1,
00718                           nonce,
00719                           nc,
00720                           cnonce,
00721                           qop,
00722                           connection->method,
00723                           uri,
00724                           hentity,
00725                           respexp);  
00726     return (0 == strcmp(response, respexp)) 
00727       ? MHD_YES 
00728       : MHD_NO;
00729   }
00730 }
00731 
00732 
00743 int
00744 MHD_queue_auth_fail_response (struct MHD_Connection *connection,
00745                               const char *realm,
00746                               const char *opaque,
00747                               struct MHD_Response *response,
00748                               int signal_stale)
00749 {
00750   int ret;
00751   size_t hlen;
00752   char nonce[HASH_MD5_HEX_LEN + 9];
00753 
00754   /* Generating the server nonce */  
00755   calculate_nonce ((uint32_t) MHD_monotonic_time(),
00756                    connection->method,
00757                    connection->daemon->digest_auth_random,
00758                    connection->daemon->digest_auth_rand_size,
00759                    connection->url,
00760                    realm,
00761                    nonce);
00762   if (MHD_YES != check_nonce_nc (connection, nonce, 0))
00763     {
00764 #if HAVE_MESSAGES
00765       MHD_DLOG (connection->daemon, 
00766                 "Could not register nonce (is the nonce array size zero?).\n");
00767 #endif
00768       return MHD_NO;  
00769     }
00770   /* Building the authentication header */
00771   hlen = snprintf (NULL,
00772                    0,
00773                    "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s",
00774                    realm, 
00775                    nonce,
00776                    opaque,
00777                    signal_stale 
00778                    ? ",stale=\"true\"" 
00779                    : "");
00780   {
00781     char header[hlen + 1];
00782 
00783     snprintf (header,
00784               sizeof(header),
00785               "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s",
00786               realm, 
00787               nonce,
00788               opaque,
00789               signal_stale 
00790               ? ",stale=\"true\"" 
00791               : "");
00792     ret = MHD_add_response_header(response,
00793                                   MHD_HTTP_HEADER_WWW_AUTHENTICATE, 
00794                                   header);
00795   }
00796   if (MHD_YES == ret) 
00797     ret = MHD_queue_response(connection, 
00798                              MHD_HTTP_UNAUTHORIZED, 
00799                              response);  
00800   return ret;
00801 }
00802 
00803 
00804 /* end of digestauth.c */

Generated on Thu Sep 27 17:56:12 2012 for GNU libmicrohttpd by  doxygen 1.4.7