digestauth.c

Go to the documentation of this file.
00001 /*
00002      This file is part of libmicrohttpd
00003      (C) 2010 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 */
00019 
00026 #include "platform.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 
00059 static void
00060 cvthex(const unsigned char *bin,
00061        size_t len,
00062        char *hex)
00063 {
00064   size_t i;
00065   unsigned int j;
00066   
00067   for (i = 0; i < len; ++i) 
00068     {
00069       j = (bin[i] >> 4) & 0x0f;      
00070       hex[i * 2] = j <= 9 ? (j + '0') : (j + 'a' - 10);    
00071       j = bin[i] & 0x0f;    
00072       hex[i * 2 + 1] = j <= 9 ? (j + '0') : (j + 'a' - 10);
00073     }
00074   hex[len * 2] = '\0';
00075 }
00076 
00089 static void
00090 digest_calc_ha1(const char *alg,
00091                 const char *username,
00092                 const char *realm,
00093                 const char *password,
00094                 const char *nonce,
00095                 const char *cnonce,
00096                 char *sessionkey)
00097 {
00098   struct MD5Context md5;
00099   unsigned char ha1[MD5_DIGEST_SIZE];
00100   
00101   MD5Init (&md5);
00102   MD5Update (&md5, username, strlen (username));
00103   MD5Update (&md5, ":", 1);
00104   MD5Update (&md5, realm, strlen (realm));
00105   MD5Update (&md5, ":", 1);
00106   MD5Update (&md5, password, strlen (password));
00107   MD5Final (ha1, &md5);
00108   if (0 == strcasecmp(alg, "md5-sess")) 
00109     {
00110       MD5Init (&md5);
00111       MD5Update (&md5, ha1, sizeof (ha1));
00112       MD5Update (&md5, ":", 1);
00113       MD5Update (&md5, nonce, strlen (nonce));
00114       MD5Update (&md5, ":", 1);
00115       MD5Update (&md5, cnonce, strlen (cnonce));
00116       MD5Final (ha1, &md5);
00117     }
00118   cvthex(ha1, sizeof (ha1), sessionkey);
00119 }
00120 
00121 
00135 static void
00136 digest_calc_response(const char *ha1,
00137                      const char *nonce,
00138                      const char *noncecount,
00139                      const char *cnonce,
00140                      const char *qop,
00141                      const char *method,
00142                      const char *uri,
00143                      const char *hentity,
00144                      char *response)
00145 {
00146   struct MD5Context md5;
00147   unsigned char ha2[MD5_DIGEST_SIZE];
00148   unsigned char resphash[MD5_DIGEST_SIZE];
00149   char ha2hex[HASH_MD5_HEX_LEN + 1];
00150   
00151   MD5Init (&md5);
00152   MD5Update (&md5, method, strlen(method));
00153   MD5Update (&md5, ":", 1);
00154   MD5Update (&md5, uri, strlen(uri)); 
00155 #if 0
00156   if (strcasecmp(qop, "auth-int") == 0) 
00157     {
00158       /* This is dead code since the rest of this module does
00159          not support auth-int. */
00160       MD5Update (&md5, ":", 1);
00161       if (hentity != NULL)
00162         MD5Update (&md5, hentity, strlen(hentity));
00163     }
00164 #endif  
00165   MD5Final (ha2, &md5);
00166   cvthex(ha2, MD5_DIGEST_SIZE, ha2hex);
00167   MD5Init (&md5);  
00168   /* calculate response */  
00169   MD5Update (&md5, ha1, HASH_MD5_HEX_LEN);
00170   MD5Update (&md5, ":", 1);
00171   MD5Update (&md5, nonce, strlen(nonce));
00172   MD5Update (&md5, ":", 1);  
00173   if ('\0' != *qop)
00174     {
00175       MD5Update (&md5, noncecount, strlen(noncecount));
00176       MD5Update (&md5, ":", 1);
00177       MD5Update (&md5, cnonce, strlen(cnonce));
00178       MD5Update (&md5, ":", 1);
00179       MD5Update (&md5, qop, strlen(qop));
00180       MD5Update (&md5, ":", 1);
00181     }  
00182   MD5Update (&md5, ha2hex, HASH_MD5_HEX_LEN);
00183   MD5Final (resphash, &md5);
00184   cvthex(resphash, sizeof (resphash), response);
00185 }
00186 
00187 
00202 static int
00203 lookup_sub_value(char *dest,
00204                  size_t size,
00205                  const char *data,
00206                  const char *key)
00207 {
00208   size_t keylen = strlen(key);
00209   size_t len;
00210   const char *ptr = data;
00211   const char *eq;
00212   const char *q1;
00213   const char *q2;
00214   const char *qn;
00215 
00216   if (0 == size)
00217     return 0;
00218   while ('\0' != *ptr)
00219     {
00220       if (NULL == (eq = strstr (ptr, "=")))
00221         return 0;
00222       q1 = eq + 1;
00223       while (' ' == *q1)
00224         q1++;      
00225       if ('\"' != *q1)
00226         {
00227           q2 = strstr (q1, ",");
00228           qn = q2;
00229         }
00230       else
00231         {
00232           q1++;
00233           q2 = strstr (q1, "\"");
00234           if (NULL == q2)
00235             return 0; /* end quote not found */
00236           qn = q2 + 1;
00237         }      
00238       if ( (0 == strncasecmp (ptr,
00239                               key,
00240                               keylen)) &&
00241            (eq == &ptr[keylen]) )
00242         {
00243           if (q2 == NULL)
00244             {
00245               len = strlen (q1) + 1;
00246               if (size > len)
00247                 size = len;
00248               size--;
00249               strncpy (dest,
00250                        q1,
00251                        size);
00252               dest[size] = '\0';
00253               return size;
00254             }
00255           else
00256             {
00257               if (size > (q2 - q1) + 1)
00258                 size = (q2 - q1) + 1;
00259               size--;
00260               memcpy (dest, 
00261                       q1,
00262                       size);
00263               dest[size] = '\0';
00264               return size;
00265             }
00266         }
00267       if (NULL == qn)
00268         return 0;
00269       ptr = strstr (qn, ",");
00270       if (NULL == ptr)
00271         return 0;
00272       ptr++;
00273       while (' ' == *ptr)
00274         ptr++;
00275     }
00276   return 0;
00277 }
00278 
00279 
00289 static int
00290 check_nonce_nc (struct MHD_Connection *connection,
00291                 const char *nonce,
00292                 unsigned int nc)
00293 {
00294   uint32_t off;
00295   uint32_t mod;
00296   const char *np;
00297 
00298   mod = connection->daemon->nonce_nc_size;
00299   if (0 == mod)
00300     return MHD_NO; /* no array! */
00301   /* super-fast xor-based "hash" function for HT lookup in nonce array */
00302   off = 0;
00303   np = nonce;
00304   while (*np != '\0')
00305     {
00306       off = (off << 8) | (*np & (off >> 24));
00307       np++;
00308     }
00309   off = off % mod;
00310   /*
00311    * Look for the nonce, if it does exist and its corresponding
00312    * nonce counter is less than the current nonce counter by 1,
00313    * then only increase the nonce counter by one.
00314    */
00315   
00316   pthread_mutex_lock(&connection->daemon->nnc_lock);
00317   if (nc == 0)
00318     {
00319       strcpy(connection->daemon->nnc[off].nonce, 
00320              nonce);
00321       connection->daemon->nnc[off].nc = 0;  
00322       pthread_mutex_unlock(&connection->daemon->nnc_lock);
00323       return MHD_YES;
00324     }
00325   if ( (nc <= connection->daemon->nnc[off].nc) ||
00326        (0 != strcmp(connection->daemon->nnc[off].nonce, nonce)) )
00327     {
00328       pthread_mutex_unlock(&connection->daemon->nnc_lock);
00329 #if HAVE_MESSAGES
00330       MHD_DLOG (connection->daemon, 
00331                 "Stale nonce received.  If this happens a lot, you should probably increase the size of the nonce array.\n");
00332 #endif
00333       return MHD_NO;
00334     }
00335   connection->daemon->nnc[off].nc = nc;
00336   pthread_mutex_unlock(&connection->daemon->nnc_lock);
00337   return MHD_YES;
00338 }
00339 
00340 
00348 char *
00349 MHD_digest_auth_get_username(struct MHD_Connection *connection)
00350 {
00351   size_t len;
00352   char user[MAX_USERNAME_LENGTH];
00353   const char *header;
00354   
00355   header = MHD_lookup_connection_value(connection,
00356                                        MHD_HEADER_KIND, 
00357                                        MHD_HTTP_HEADER_AUTHORIZATION); 
00358   if (header == NULL)
00359     return NULL;
00360   if (strncmp(header, _BASE, strlen(_BASE)) != 0)
00361     return NULL;
00362   header += strlen (_BASE);
00363   len = lookup_sub_value(user,
00364                          sizeof (user),
00365                          header, 
00366                          "username");
00367   if (!len)
00368     return NULL;
00369   return strdup(user);
00370 }
00371 
00372 
00386 static void
00387 calculate_nonce (uint32_t nonce_time,
00388                  const char *method,
00389                  const char *rnd,
00390                  unsigned int rnd_size,
00391                  const char *uri,
00392                  const char *realm,
00393                  char *nonce)
00394 {
00395   struct MD5Context md5;
00396   unsigned char timestamp[4];
00397   unsigned char tmpnonce[MD5_DIGEST_SIZE];
00398   char timestamphex[sizeof(timestamp)*2+1];
00399 
00400   MD5Init (&md5);
00401   timestamp[0] = (nonce_time & 0xff000000) >> 0x18;
00402   timestamp[1] = (nonce_time & 0x00ff0000) >> 0x10;
00403   timestamp[2] = (nonce_time & 0x0000ff00) >> 0x08;
00404   timestamp[3] = (nonce_time & 0x000000ff);    
00405   MD5Update(&md5, timestamp, 4);
00406   MD5Update(&md5, ":", 1);
00407   MD5Update(&md5, method, strlen(method));
00408   MD5Update(&md5, ":", 1);
00409   if (rnd_size > 0)
00410     MD5Update(&md5, rnd, rnd_size);
00411   MD5Update(&md5, ":", 1);
00412   MD5Update(&md5, uri, strlen(uri));
00413   MD5Update(&md5, ":", 1);
00414   MD5Update(&md5, realm, strlen(realm));
00415   MD5Final (tmpnonce, &md5);  
00416   cvthex(tmpnonce, sizeof (tmpnonce), nonce);  
00417   cvthex(timestamp, 4, timestamphex);
00418   strncat(nonce, timestamphex, 8);
00419 }
00420 
00421 
00434 int
00435 MHD_digest_auth_check(struct MHD_Connection *connection,
00436                       const char *realm,
00437                       const char *username,
00438                       const char *password,
00439                       unsigned int nonce_timeout)
00440 {
00441   size_t len;
00442   const char *header;
00443   char nonce[MAX_NONCE_LENGTH];
00444   char cnonce[MAX_NONCE_LENGTH];
00445   char qop[15]; /* auth,auth-int */
00446   char nc[20];
00447   char response[MAX_AUTH_RESPONSE_LENGTH];
00448   const char *hentity = NULL; /* "auth-int" is not supported */
00449   char ha1[HASH_MD5_HEX_LEN + 1];
00450   char respexp[HASH_MD5_HEX_LEN + 1];
00451   char noncehashexp[HASH_MD5_HEX_LEN + 9];
00452   uint32_t nonce_time;
00453   uint32_t t;
00454   size_t left; /* number of characters left in 'header' for 'uri' */
00455   unsigned int nci;
00456 
00457   header = MHD_lookup_connection_value(connection,
00458                                        MHD_HEADER_KIND,
00459                                        MHD_HTTP_HEADER_AUTHORIZATION);  
00460   if (header == NULL) 
00461     return MHD_NO;
00462   if (strncmp(header, _BASE, strlen(_BASE)) != 0) 
00463     return MHD_NO;
00464   header += strlen (_BASE);
00465   left = strlen (header);
00466 
00467   {
00468     char un[MAX_USERNAME_LENGTH];
00469     len = lookup_sub_value(un,
00470                            sizeof (un),
00471                            header, "username");
00472     if ( (!len) ||
00473          (strcmp(username, un) != 0) ) 
00474       return MHD_NO;
00475     left -= strlen ("username") + len;
00476   }
00477 
00478   {
00479     char r[MAX_REALM_LENGTH];
00480     len = lookup_sub_value(r, 
00481                            sizeof (r),
00482                            header, "realm");  
00483     if ( (!len) || 
00484          (strcmp(realm, r) != 0) )
00485       return MHD_NO;
00486     left -= strlen ("realm") + len;
00487   }
00488 
00489   if (0 == (len = lookup_sub_value(nonce, 
00490                                    sizeof (nonce),
00491                                    header, "nonce")))
00492     return MHD_NO;
00493   left -= strlen ("nonce") + len;
00494 
00495   {
00496     char uri[left];  
00497   
00498     if (0 == lookup_sub_value(uri,
00499                               sizeof (uri),
00500                               header, "uri")) 
00501       return MHD_NO;
00502       
00503     /* 8 = 4 hexadecimal numbers for the timestamp */  
00504     nonce_time = strtoul(nonce + len - 8, (char **)NULL, 16);  
00505     t = (uint32_t) time(NULL);    
00506     /*
00507      * First level vetting for the nonce validity
00508      * if the timestamp attached to the nonce
00509      * exceeds `nonce_timeout' then the nonce is
00510      * invalid.
00511      */
00512     if (t > nonce_time + nonce_timeout) 
00513       return MHD_INVALID_NONCE;    
00514     calculate_nonce (nonce_time,
00515                      connection->method,
00516                      connection->daemon->digest_auth_random,
00517                      connection->daemon->digest_auth_rand_size,
00518                      uri,
00519                      realm,
00520                      noncehashexp);
00521     /*
00522      * Second level vetting for the nonce validity
00523      * if the timestamp attached to the nonce is valid
00524      * and possibly fabricated (in case of an attack)
00525      * the attacker must also know the random seed to be
00526      * able to generate a "sane" nonce, which if he does
00527      * not, the nonce fabrication process going to be
00528      * very hard to achieve.
00529      */
00530     
00531     if (0 != strcmp(nonce, noncehashexp))
00532       return MHD_INVALID_NONCE;
00533     if ( (0 == lookup_sub_value(cnonce,
00534                                 sizeof (cnonce), 
00535                                 header, "cnonce")) ||
00536          (0 == lookup_sub_value(qop, sizeof (qop), header, "qop")) ||
00537          ( (0 != strcmp (qop, "auth")) && 
00538            (0 != strcmp (qop, "")) ) ||
00539          (0 == lookup_sub_value(nc, sizeof (nc), header, "nc"))  ||
00540          (1 != sscanf (nc, "%u", &nci)) ||
00541          (0 == lookup_sub_value(response, sizeof (response), header, "response")) )
00542       return MHD_NO;
00543     
00544     /*
00545      * Checking if that combination of nonce and nc is sound
00546      * and not a replay attack attempt. Also adds the nonce
00547      * to the nonce-nc map if it does not exist there.
00548      */
00549     
00550     if (MHD_YES != check_nonce_nc (connection, nonce, nci))
00551       return MHD_NO;
00552     
00553     digest_calc_ha1("md5",
00554                     username,
00555                     realm,
00556                     password,
00557                     nonce,
00558                     cnonce,
00559                     ha1);
00560     digest_calc_response(ha1,
00561                          nonce,
00562                          nc,
00563                          cnonce,
00564                          qop,
00565                          connection->method,
00566                          uri,
00567                          hentity,
00568                          respexp);  
00569     return strcmp(response, respexp) == 0 ? MHD_YES : MHD_NO;
00570   }
00571 }
00572 
00573 
00584 int
00585 MHD_queue_auth_fail_response(struct MHD_Connection *connection,
00586                              const char *realm,
00587                              const char *opaque,
00588                              struct MHD_Response *response,
00589                              int signal_stale)
00590 {
00591   int ret;
00592   size_t hlen;
00593   char nonce[HASH_MD5_HEX_LEN + 9];
00594 
00595   /* Generating the server nonce */  
00596   calculate_nonce ((uint32_t) time(NULL),
00597                    connection->method,
00598                    connection->daemon->digest_auth_random,
00599                    connection->daemon->digest_auth_rand_size,
00600                    connection->url,
00601                    realm,
00602                    nonce);
00603   if (MHD_YES != check_nonce_nc (connection, nonce, 0))
00604     {
00605 #if HAVE_MESSAGES
00606       MHD_DLOG (connection->daemon, 
00607                 "Could not register nonce (is the nonce array size zero?).\n");
00608 #endif
00609       return MHD_NO;  
00610     }
00611   /* Building the authentication header */
00612   hlen = snprintf(NULL,
00613                   0,
00614                   "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s",
00615                   realm, 
00616                   nonce,
00617                   opaque,
00618                   signal_stale ? ",stale=\"true\"" : "");
00619   {
00620     char header[hlen + 1];
00621     snprintf(header,
00622              sizeof(header),
00623              "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s",
00624              realm, 
00625              nonce,
00626              opaque,
00627              signal_stale ? ",stale=\"true\"" : "");
00628     ret = MHD_add_response_header(response,
00629                                   MHD_HTTP_HEADER_WWW_AUTHENTICATE, 
00630                                   header);
00631   }
00632   if (MHD_YES == ret) 
00633     ret = MHD_queue_response(connection, 
00634                              MHD_HTTP_UNAUTHORIZED, 
00635                              response);  
00636   return ret;
00637 }
00638 
00639 
00640 /* end of digestauth.c */

Generated on 16 Nov 2010 for GNU libmicrohttpd by  doxygen 1.6.1