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 
00027 #include "platform.h"
00028 #include "internal.h"
00029 #include "md5.h"
00030 #include "base64.h"
00031 
00032 #define HASH_MD5_HEX_LEN (2 * MD5_DIGEST_SIZE)
00033 
00037 #define _BASE           "Digest "
00038 
00042 #define _BASIC_BASE             "Basic "
00043 
00047 #define MAX_USERNAME_LENGTH 128
00048 
00052 #define MAX_REALM_LENGTH 256
00053 
00057 #define MAX_AUTH_RESPONSE_LENGTH 128
00058 
00066 static void
00067 cvthex(const unsigned char *bin,
00068        size_t len,
00069        char *hex)
00070 {
00071   size_t i;
00072   unsigned int j;
00073   
00074   for (i = 0; i < len; ++i) 
00075     {
00076       j = (bin[i] >> 4) & 0x0f;      
00077       hex[i * 2] = j <= 9 ? (j + '0') : (j + 'a' - 10);    
00078       j = bin[i] & 0x0f;    
00079       hex[i * 2 + 1] = j <= 9 ? (j + '0') : (j + 'a' - 10);
00080     }
00081   hex[len * 2] = '\0';
00082 }
00083 
00096 static void
00097 digest_calc_ha1(const char *alg,
00098                 const char *username,
00099                 const char *realm,
00100                 const char *password,
00101                 const char *nonce,
00102                 const char *cnonce,
00103                 char *sessionkey)
00104 {
00105   struct MD5Context md5;
00106   unsigned char ha1[MD5_DIGEST_SIZE];
00107   
00108   MD5Init (&md5);
00109   MD5Update (&md5, username, strlen (username));
00110   MD5Update (&md5, ":", 1);
00111   MD5Update (&md5, realm, strlen (realm));
00112   MD5Update (&md5, ":", 1);
00113   MD5Update (&md5, password, strlen (password));
00114   MD5Final (ha1, &md5);
00115   if (0 == strcasecmp(alg, "md5-sess")) 
00116     {
00117       MD5Init (&md5);
00118       MD5Update (&md5, ha1, sizeof (ha1));
00119       MD5Update (&md5, ":", 1);
00120       MD5Update (&md5, nonce, strlen (nonce));
00121       MD5Update (&md5, ":", 1);
00122       MD5Update (&md5, cnonce, strlen (cnonce));
00123       MD5Final (ha1, &md5);
00124     }
00125   cvthex(ha1, sizeof (ha1), sessionkey);
00126 }
00127 
00128 
00142 static void
00143 digest_calc_response(const char *ha1,
00144                      const char *nonce,
00145                      const char *noncecount,
00146                      const char *cnonce,
00147                      const char *qop,
00148                      const char *method,
00149                      const char *uri,
00150                      const char *hentity,
00151                      char *response)
00152 {
00153   struct MD5Context md5;
00154   unsigned char ha2[MD5_DIGEST_SIZE];
00155   unsigned char resphash[MD5_DIGEST_SIZE];
00156   char ha2hex[HASH_MD5_HEX_LEN + 1];
00157   
00158   MD5Init (&md5);
00159   MD5Update (&md5, method, strlen(method));
00160   MD5Update (&md5, ":", 1);
00161   MD5Update (&md5, uri, strlen(uri)); 
00162 #if 0
00163   if (strcasecmp(qop, "auth-int") == 0) 
00164     {
00165       /* This is dead code since the rest of this module does
00166          not support auth-int. */
00167       MD5Update (&md5, ":", 1);
00168       if (hentity != NULL)
00169         MD5Update (&md5, hentity, strlen(hentity));
00170     }
00171 #endif  
00172   MD5Final (ha2, &md5);
00173   cvthex(ha2, MD5_DIGEST_SIZE, ha2hex);
00174   MD5Init (&md5);  
00175   /* calculate response */  
00176   MD5Update (&md5, ha1, HASH_MD5_HEX_LEN);
00177   MD5Update (&md5, ":", 1);
00178   MD5Update (&md5, nonce, strlen(nonce));
00179   MD5Update (&md5, ":", 1);  
00180   if ('\0' != *qop)
00181     {
00182       MD5Update (&md5, noncecount, strlen(noncecount));
00183       MD5Update (&md5, ":", 1);
00184       MD5Update (&md5, cnonce, strlen(cnonce));
00185       MD5Update (&md5, ":", 1);
00186       MD5Update (&md5, qop, strlen(qop));
00187       MD5Update (&md5, ":", 1);
00188     }  
00189   MD5Update (&md5, ha2hex, HASH_MD5_HEX_LEN);
00190   MD5Final (resphash, &md5);
00191   cvthex(resphash, sizeof (resphash), response);
00192 }
00193 
00194 
00209 static int
00210 lookup_sub_value(char *dest,
00211                  size_t size,
00212                  const char *data,
00213                  const char *key)
00214 {
00215   size_t keylen = strlen(key);
00216   size_t len;
00217   const char *ptr = data;
00218   const char *eq;
00219   const char *q1;
00220   const char *q2;
00221   const char *qn;
00222 
00223   if (0 == size)
00224     return 0;
00225   while ('\0' != *ptr)
00226     {
00227       if (NULL == (eq = strstr (ptr, "=")))
00228         return 0;
00229       q1 = eq + 1;
00230       while (' ' == *q1)
00231         q1++;      
00232       if ('\"' != *q1)
00233         {
00234           q2 = strstr (q1, ",");
00235           qn = q2;
00236         }
00237       else
00238         {
00239           q1++;
00240           q2 = strstr (q1, "\"");
00241           if (NULL == q2)
00242             return 0; /* end quote not found */
00243           qn = q2 + 1;
00244         }      
00245       if ( (0 == strncasecmp (ptr,
00246                               key,
00247                               keylen)) &&
00248            (eq == &ptr[keylen]) )
00249         {
00250           if (q2 == NULL)
00251             {
00252               len = strlen (q1) + 1;
00253               if (size > len)
00254                 size = len;
00255               size--;
00256               strncpy (dest,
00257                        q1,
00258                        size);
00259               dest[size] = '\0';
00260               return size;
00261             }
00262           else
00263             {
00264               if (size > (q2 - q1) + 1)
00265                 size = (q2 - q1) + 1;
00266               size--;
00267               memcpy (dest, 
00268                       q1,
00269                       size);
00270               dest[size] = '\0';
00271               return size;
00272             }
00273         }
00274       if (NULL == qn)
00275         return 0;
00276       ptr = strstr (qn, ",");
00277       if (NULL == ptr)
00278         return 0;
00279       ptr++;
00280       while (' ' == *ptr)
00281         ptr++;
00282     }
00283   return 0;
00284 }
00285 
00286 
00296 static int
00297 check_nonce_nc (struct MHD_Connection *connection,
00298                 const char *nonce,
00299                 unsigned int nc)
00300 {
00301   uint32_t off;
00302   uint32_t mod;
00303   const char *np;
00304 
00305   mod = connection->daemon->nonce_nc_size;
00306   if (0 == mod)
00307     return MHD_NO; /* no array! */
00308   /* super-fast xor-based "hash" function for HT lookup in nonce array */
00309   off = 0;
00310   np = nonce;
00311   while (*np != '\0')
00312     {
00313       off = (off << 8) | (*np ^ (off >> 24));
00314       np++;
00315     }
00316   off = off % mod;
00317   /*
00318    * Look for the nonce, if it does exist and its corresponding
00319    * nonce counter is less than the current nonce counter by 1,
00320    * then only increase the nonce counter by one.
00321    */
00322   
00323   pthread_mutex_lock(&connection->daemon->nnc_lock);
00324   if (nc == 0)
00325     {
00326       strcpy(connection->daemon->nnc[off].nonce, 
00327              nonce);
00328       connection->daemon->nnc[off].nc = 0;  
00329       pthread_mutex_unlock(&connection->daemon->nnc_lock);
00330       return MHD_YES;
00331     }
00332   if ( (nc <= connection->daemon->nnc[off].nc) ||
00333        (0 != strcmp(connection->daemon->nnc[off].nonce, nonce)) )
00334     {
00335       pthread_mutex_unlock(&connection->daemon->nnc_lock);
00336 #if HAVE_MESSAGES
00337       MHD_DLOG (connection->daemon, 
00338                 "Stale nonce received.  If this happens a lot, you should probably increase the size of the nonce array.\n");
00339 #endif
00340       return MHD_NO;
00341     }
00342   connection->daemon->nnc[off].nc = nc;
00343   pthread_mutex_unlock(&connection->daemon->nnc_lock);
00344   return MHD_YES;
00345 }
00346 
00347 
00355 char *
00356 MHD_digest_auth_get_username(struct MHD_Connection *connection)
00357 {
00358   size_t len;
00359   char user[MAX_USERNAME_LENGTH];
00360   const char *header;
00361   
00362   header = MHD_lookup_connection_value(connection,
00363                                        MHD_HEADER_KIND, 
00364                                        MHD_HTTP_HEADER_AUTHORIZATION); 
00365   if (header == NULL)
00366     return NULL;
00367   if (strncmp(header, _BASE, strlen(_BASE)) != 0)
00368     return NULL;
00369   header += strlen (_BASE);
00370   len = lookup_sub_value(user,
00371                          sizeof (user),
00372                          header, 
00373                          "username");
00374   if (!len)
00375     return NULL;
00376   return strdup(user);
00377 }
00378 
00379 
00393 static void
00394 calculate_nonce (uint32_t nonce_time,
00395                  const char *method,
00396                  const char *rnd,
00397                  unsigned int rnd_size,
00398                  const char *uri,
00399                  const char *realm,
00400                  char *nonce)
00401 {
00402   struct MD5Context md5;
00403   unsigned char timestamp[4];
00404   unsigned char tmpnonce[MD5_DIGEST_SIZE];
00405   char timestamphex[sizeof(timestamp)*2+1];
00406 
00407   MD5Init (&md5);
00408   timestamp[0] = (nonce_time & 0xff000000) >> 0x18;
00409   timestamp[1] = (nonce_time & 0x00ff0000) >> 0x10;
00410   timestamp[2] = (nonce_time & 0x0000ff00) >> 0x08;
00411   timestamp[3] = (nonce_time & 0x000000ff);    
00412   MD5Update(&md5, timestamp, 4);
00413   MD5Update(&md5, ":", 1);
00414   MD5Update(&md5, method, strlen(method));
00415   MD5Update(&md5, ":", 1);
00416   if (rnd_size > 0)
00417     MD5Update(&md5, rnd, rnd_size);
00418   MD5Update(&md5, ":", 1);
00419   MD5Update(&md5, uri, strlen(uri));
00420   MD5Update(&md5, ":", 1);
00421   MD5Update(&md5, realm, strlen(realm));
00422   MD5Final (tmpnonce, &md5);  
00423   cvthex(tmpnonce, sizeof (tmpnonce), nonce);  
00424   cvthex(timestamp, 4, timestamphex);
00425   strncat(nonce, timestamphex, 8);
00426 }
00427 
00428 
00441 int
00442 MHD_digest_auth_check(struct MHD_Connection *connection,
00443                       const char *realm,
00444                       const char *username,
00445                       const char *password,
00446                       unsigned int nonce_timeout)
00447 {
00448   size_t len;
00449   const char *header;
00450   char nonce[MAX_NONCE_LENGTH];
00451   char cnonce[MAX_NONCE_LENGTH];
00452   char qop[15]; /* auth,auth-int */
00453   char nc[20];
00454   char response[MAX_AUTH_RESPONSE_LENGTH];
00455   const char *hentity = NULL; /* "auth-int" is not supported */
00456   char ha1[HASH_MD5_HEX_LEN + 1];
00457   char respexp[HASH_MD5_HEX_LEN + 1];
00458   char noncehashexp[HASH_MD5_HEX_LEN + 9];
00459   uint32_t nonce_time;
00460   uint32_t t;
00461   size_t left; /* number of characters left in 'header' for 'uri' */
00462   unsigned int nci;
00463 
00464   header = MHD_lookup_connection_value(connection,
00465                                        MHD_HEADER_KIND,
00466                                        MHD_HTTP_HEADER_AUTHORIZATION);  
00467   if (header == NULL) 
00468     return MHD_NO;
00469   if (strncmp(header, _BASE, strlen(_BASE)) != 0) 
00470     return MHD_NO;
00471   header += strlen (_BASE);
00472   left = strlen (header);
00473 
00474   {
00475     char un[MAX_USERNAME_LENGTH];
00476     len = lookup_sub_value(un,
00477                            sizeof (un),
00478                            header, "username");
00479     if ( (!len) ||
00480          (strcmp(username, un) != 0) ) 
00481       return MHD_NO;
00482     left -= strlen ("username") + len;
00483   }
00484 
00485   {
00486     char r[MAX_REALM_LENGTH];
00487     len = lookup_sub_value(r, 
00488                            sizeof (r),
00489                            header, "realm");  
00490     if ( (!len) || 
00491          (strcmp(realm, r) != 0) )
00492       return MHD_NO;
00493     left -= strlen ("realm") + len;
00494   }
00495 
00496   if (0 == (len = lookup_sub_value(nonce, 
00497                                    sizeof (nonce),
00498                                    header, "nonce")))
00499     return MHD_NO;
00500   left -= strlen ("nonce") + len;
00501 
00502   {
00503     char uri[left];  
00504   
00505     if (0 == lookup_sub_value(uri,
00506                               sizeof (uri),
00507                               header, "uri")) 
00508       return MHD_NO;
00509       
00510     /* 8 = 4 hexadecimal numbers for the timestamp */  
00511     nonce_time = strtoul(nonce + len - 8, (char **)NULL, 16);  
00512     t = (uint32_t) time(NULL);    
00513     /*
00514      * First level vetting for the nonce validity
00515      * if the timestamp attached to the nonce
00516      * exceeds `nonce_timeout' then the nonce is
00517      * invalid.
00518      */
00519     if (t > nonce_time + nonce_timeout) 
00520       return MHD_INVALID_NONCE;    
00521     calculate_nonce (nonce_time,
00522                      connection->method,
00523                      connection->daemon->digest_auth_random,
00524                      connection->daemon->digest_auth_rand_size,
00525                      uri,
00526                      realm,
00527                      noncehashexp);
00528     /*
00529      * Second level vetting for the nonce validity
00530      * if the timestamp attached to the nonce is valid
00531      * and possibly fabricated (in case of an attack)
00532      * the attacker must also know the random seed to be
00533      * able to generate a "sane" nonce, which if he does
00534      * not, the nonce fabrication process going to be
00535      * very hard to achieve.
00536      */
00537     
00538     if (0 != strcmp(nonce, noncehashexp))
00539       return MHD_INVALID_NONCE;
00540     if ( (0 == lookup_sub_value(cnonce,
00541                                 sizeof (cnonce), 
00542                                 header, "cnonce")) ||
00543          (0 == lookup_sub_value(qop, sizeof (qop), header, "qop")) ||
00544          ( (0 != strcmp (qop, "auth")) && 
00545            (0 != strcmp (qop, "")) ) ||
00546          (0 == lookup_sub_value(nc, sizeof (nc), header, "nc"))  ||
00547          (1 != sscanf (nc, "%u", &nci)) ||
00548          (0 == lookup_sub_value(response, sizeof (response), header, "response")) )
00549       return MHD_NO;
00550     
00551     /*
00552      * Checking if that combination of nonce and nc is sound
00553      * and not a replay attack attempt. Also adds the nonce
00554      * to the nonce-nc map if it does not exist there.
00555      */
00556     
00557     if (MHD_YES != check_nonce_nc (connection, nonce, nci))
00558       return MHD_NO;
00559     
00560     digest_calc_ha1("md5",
00561                     username,
00562                     realm,
00563                     password,
00564                     nonce,
00565                     cnonce,
00566                     ha1);
00567     digest_calc_response(ha1,
00568                          nonce,
00569                          nc,
00570                          cnonce,
00571                          qop,
00572                          connection->method,
00573                          uri,
00574                          hentity,
00575                          respexp);  
00576     return strcmp(response, respexp) == 0 ? MHD_YES : MHD_NO;
00577   }
00578 }
00579 
00580 
00591 int
00592 MHD_queue_auth_fail_response(struct MHD_Connection *connection,
00593                              const char *realm,
00594                              const char *opaque,
00595                              struct MHD_Response *response,
00596                              int signal_stale)
00597 {
00598   int ret;
00599   size_t hlen;
00600   char nonce[HASH_MD5_HEX_LEN + 9];
00601 
00602   /* Generating the server nonce */  
00603   calculate_nonce ((uint32_t) time(NULL),
00604                    connection->method,
00605                    connection->daemon->digest_auth_random,
00606                    connection->daemon->digest_auth_rand_size,
00607                    connection->url,
00608                    realm,
00609                    nonce);
00610   if (MHD_YES != check_nonce_nc (connection, nonce, 0))
00611     {
00612 #if HAVE_MESSAGES
00613       MHD_DLOG (connection->daemon, 
00614                 "Could not register nonce (is the nonce array size zero?).\n");
00615 #endif
00616       return MHD_NO;  
00617     }
00618   /* Building the authentication header */
00619   hlen = snprintf(NULL,
00620                   0,
00621                   "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s",
00622                   realm, 
00623                   nonce,
00624                   opaque,
00625                   signal_stale ? ",stale=\"true\"" : "");
00626   {
00627     char header[hlen + 1];
00628     snprintf(header,
00629              sizeof(header),
00630              "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s",
00631              realm, 
00632              nonce,
00633              opaque,
00634              signal_stale ? ",stale=\"true\"" : "");
00635     ret = MHD_add_response_header(response,
00636                                   MHD_HTTP_HEADER_WWW_AUTHENTICATE, 
00637                                   header);
00638   }
00639   if (MHD_YES == ret) 
00640     ret = MHD_queue_response(connection, 
00641                              MHD_HTTP_UNAUTHORIZED, 
00642                              response);  
00643   return ret;
00644 }
00645 
00646 
00655 char *
00656 MHD_basic_auth_get_username_password(struct MHD_Connection *connection,
00657                                      char** password) 
00658 {
00659   const char *header;
00660   char *decode;
00661   const char *separator;
00662   char *user;
00663   
00664   header = MHD_lookup_connection_value(connection, 
00665                                        MHD_HEADER_KIND,
00666                                        MHD_HTTP_HEADER_AUTHORIZATION);
00667   if (header == NULL)
00668     return NULL;
00669   if (strncmp(header, _BASIC_BASE, strlen(_BASIC_BASE)) != 0)
00670     return NULL;
00671   header += strlen(_BASIC_BASE);
00672   decode = BASE64Decode(header);
00673   if (decode == NULL) 
00674     {
00675 #if HAVE_MESSAGES
00676       MHD_DLOG(connection->daemon,
00677                "Error decoding basic authentication\n");
00678 #endif
00679       return NULL;
00680     }
00681   /* Find user:password pattern */
00682   separator = strstr(decode, ":");
00683   if (separator == NULL) 
00684     {
00685 #if HAVE_MESSAGES
00686       MHD_DLOG(connection->daemon,
00687                "Basic authentication doesn't contain ':' separator\n");
00688 #endif
00689       free(decode);
00690       return NULL;
00691     }
00692   user = strdup(decode);
00693   if (NULL == user)
00694     {
00695       free (decode);
00696       return NULL;
00697     }
00698   user[separator - decode] = '\0'; /* cut off at ':' */
00699   if (password != NULL) 
00700     {
00701       *password = strdup(separator + 1);  
00702       if (NULL == *password)
00703         {
00704 #if HAVE_MESSAGES
00705           MHD_DLOG(connection->daemon,
00706                    "Failed to allocate memory for password\n");
00707 #endif
00708           free (decode);
00709           free (user);
00710           return NULL;
00711         }
00712     }
00713   free(decode);
00714   return user;
00715 }
00716 
00717 
00725 int 
00726 MHD_queue_basic_auth_fail_response(struct MHD_Connection *connection,
00727                                    const char *realm, 
00728                                    struct MHD_Response *response) 
00729 {
00730   int ret;
00731   size_t hlen = strlen(realm) + strlen("Basic realm=\"\"");
00732   char header[hlen + 1];
00733 
00734   if (hlen !=
00735       snprintf(header, 
00736                hlen + 1, 
00737                "Basic realm=\"%s\"", 
00738                realm))
00739     {
00740       EXTRA_CHECK (0);
00741       return MHD_NO;
00742     }
00743   ret = MHD_add_response_header(response,
00744                                 MHD_HTTP_HEADER_WWW_AUTHENTICATE,
00745                                 header);
00746   if (MHD_YES == ret)
00747     ret = MHD_queue_response(connection, 
00748                              MHD_HTTP_UNAUTHORIZED, 
00749                              response);
00750   return ret;
00751 }
00752 
00753 /* end of digestauth.c */