Sergey Pastor / 1

Dependents:   Nucleo

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers http_server_auth.c Source File

http_server_auth.c

Go to the documentation of this file.
00001 /**
00002  * @file http_server_auth.c
00003  * @brief HTTP authentication
00004  *
00005  * @section License
00006  *
00007  * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved.
00008  *
00009  * This file is part of CycloneTCP Open.
00010  *
00011  * This program is free software; you can redistribute it and/or
00012  * modify it under the terms of the GNU General Public License
00013  * as published by the Free Software Foundation; either version 2
00014  * of the License, or (at your option) any later version.
00015  *
00016  * This program is distributed in the hope that it will be useful,
00017  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019  * GNU General Public License for more details.
00020  *
00021  * You should have received a copy of the GNU General Public License
00022  * along with this program; if not, write to the Free Software Foundation,
00023  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00024  *
00025  * @author Oryx Embedded SARL (www.oryx-embedded.com)
00026  * @version 1.7.6
00027  **/
00028 
00029 //Switch to the appropriate trace level
00030 #define TRACE_LEVEL HTTP_TRACE_LEVEL
00031 
00032 //Dependencies
00033 #include <stdlib.h>
00034 #include "core/net.h"
00035 #include "http/http_server.h"
00036 #include "http/http_server_auth.h"
00037 #include "http/http_server_misc.h"
00038 #include "str.h"
00039 #include "debug.h"
00040 
00041 //Check TCP/IP stack configuration
00042 #if (HTTP_SERVER_SUPPORT == ENABLED)
00043 
00044 
00045 /**
00046  * @brief Password verification
00047  * @param[in] connection Structure representing an HTTP connection
00048  * @param[in] password NULL-terminated string containing the password to be checked
00049  * @param[in] mode HTTP authentication scheme to be used. Acceptable
00050  *   values are HTTP_AUTH_MODE_BASIC or HTTP_AUTH_MODE_DIGEST
00051  * @return TRUE if the password is valid, else FALSE
00052  **/
00053 
00054 bool_t httpCheckPassword(HttpConnection *connection,
00055    const char_t *password, HttpAuthMode mode)
00056 {
00057    //This flag tells whether the password is valid
00058    bool_t status = FALSE;
00059 
00060    //Debug message
00061    TRACE_DEBUG("HTTP password verification...\r\n");
00062 
00063 #if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED)
00064    //Basic authentication scheme?
00065    if(mode == HTTP_AUTH_MODE_BASIC)
00066    {
00067       //Point to the authentication credentials
00068       HttpAuthorizationHeader *auth = &connection->request.auth;
00069 
00070       //Make sure authentication credentials have been found
00071       if(auth->found && auth->mode == HTTP_AUTH_MODE_BASIC)
00072       {
00073          //Sanity check
00074          if(auth->password != NULL)
00075          {
00076             //Check whether the password is valid
00077             if(!strcmp(password, auth->password))
00078                status = TRUE;
00079          }
00080       }
00081    }
00082 #endif
00083 #if (HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
00084    //Digest authentication scheme?
00085    if(mode == HTTP_AUTH_MODE_DIGEST)
00086    {
00087       //Point to the authentication credentials
00088       HttpAuthorizationHeader *auth = &connection->request.auth;
00089 
00090       //Make sure authentication credentials have been found
00091       if(auth->found && auth->mode == HTTP_AUTH_MODE_DIGEST)
00092       {
00093          //Sanity check
00094          if(auth->realm != NULL && auth->nonce != NULL &&
00095             auth->uri != NULL && auth->qop != NULL &&
00096             auth->nc != NULL && auth->cnonce != NULL &&
00097             auth->response != NULL)
00098          {
00099             error_t error;
00100             Md5Context *md5Context;
00101             char_t ha1[2 * MD5_DIGEST_SIZE + 1];
00102             char_t ha2[2 * MD5_DIGEST_SIZE + 1];
00103 
00104             //Allocate a memory buffer to hold the MD5 context
00105             md5Context = osAllocMem(sizeof(Md5Context));
00106 
00107             //MD5 context successfully allocated?
00108             if(md5Context != NULL)
00109             {
00110                //Compute HA1 = MD5(username : realm : password)
00111                md5Init(md5Context);
00112                md5Update(md5Context, auth->user, strlen(auth->user));
00113                md5Update(md5Context, ":", 1);
00114                md5Update(md5Context, auth->realm, strlen(auth->realm));
00115                md5Update(md5Context, ":", 1);
00116                md5Update(md5Context, password, strlen(password));
00117                md5Final(md5Context, NULL);
00118 
00119                //Convert MD5 hash to hex string
00120                httpConvertArrayToHexString(md5Context->digest, MD5_DIGEST_SIZE, ha1);
00121                //Debug message
00122                TRACE_DEBUG("  HA1: %s\r\n", ha1);
00123 
00124                //Compute HA2 = MD5(method : uri)
00125                md5Init(md5Context);
00126                md5Update(md5Context, connection->request.method, strlen(connection->request.method));
00127                md5Update(md5Context, ":", 1);
00128                md5Update(md5Context, auth->uri, strlen(auth->uri));
00129                md5Final(md5Context, NULL);
00130 
00131                //Convert MD5 hash to hex string
00132                httpConvertArrayToHexString(md5Context->digest, MD5_DIGEST_SIZE, ha2);
00133                //Debug message
00134                TRACE_DEBUG("  HA2: %s\r\n", ha2);
00135 
00136                //Compute MD5(HA1 : nonce : nc : cnonce : qop : HA1)
00137                md5Init(md5Context);
00138                md5Update(md5Context, ha1, strlen(ha1));
00139                md5Update(md5Context, ":", 1);
00140                md5Update(md5Context, auth->nonce, strlen(auth->nonce));
00141                md5Update(md5Context, ":", 1);
00142                md5Update(md5Context, auth->nc, strlen(auth->nc));
00143                md5Update(md5Context, ":", 1);
00144                md5Update(md5Context, auth->cnonce, strlen(auth->cnonce));
00145                md5Update(md5Context, ":", 1);
00146                md5Update(md5Context, auth->qop, strlen(auth->qop));
00147                md5Update(md5Context, ":", 1);
00148                md5Update(md5Context, ha2, strlen(ha2));
00149                md5Final(md5Context, NULL);
00150 
00151                //Convert MD5 hash to hex string
00152                httpConvertArrayToHexString(md5Context->digest, MD5_DIGEST_SIZE, ha1);
00153                //Debug message
00154                TRACE_DEBUG("  response: %s\r\n", ha1);
00155 
00156                //Release MD5 context
00157                osFreeMem(md5Context);
00158 
00159                //Check response
00160                if(!strcasecmp(auth->response, ha1))
00161                {
00162                   //Perform nonce verification
00163                   error = httpVerifyNonce(connection->serverContext, auth->nonce, auth->nc);
00164 
00165                   //Valid nonce?
00166                   if(!error)
00167                   {
00168                      //Access to the resource is granted
00169                      status = TRUE;
00170                   }
00171                   else
00172                   {
00173                      //The client may wish to simply retry the request with a
00174                      //new encrypted response, without re-prompting the user
00175                      //for a new username and password
00176                      connection->response.auth.stale = TRUE;
00177                   }
00178                }
00179             }
00180          }
00181       }
00182    }
00183 #endif
00184 
00185    //Return TRUE is the password is valid, else FALSE
00186    return status;
00187 }
00188 
00189 
00190 /**
00191  * @brief Parse Authorization header field
00192  * @param[in] connection Structure representing an HTTP connection
00193  * @param[in] value Authorization field value
00194  **/
00195 
00196 void httpParseAuthorizationField(HttpConnection *connection, char_t *value)
00197 {
00198    char_t *p;
00199    char_t *token;
00200 
00201    //Retrieve the authentication scheme
00202    token = strtok_r(value, " \t", &p);
00203 
00204    //Any parsing error?
00205    if(token == NULL)
00206    {
00207       //Exit immediately
00208       return;
00209    }
00210 #if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED)
00211    //Basic access authentication?
00212    else if(!strcasecmp(token, "Basic"))
00213    {
00214       error_t error;
00215       size_t n;
00216       char_t *separator;
00217 
00218       //Use the relevant authentication scheme
00219       connection->request.auth.mode = HTTP_AUTH_MODE_BASIC;
00220       //Retrieve the credentials
00221       token = strtok_r(NULL, " \t", &p);
00222 
00223       //Any parsing error?
00224       if(token != NULL)
00225       {
00226          //Decrypt the Base64 encoded string
00227          error = base64Decode(token, strlen(token), token, &n);
00228 
00229          //Successful decoding?
00230          if(!error)
00231          {
00232             //Properly terminate the string
00233             token[n] = '\0';
00234             //Check whether a separator is present
00235             separator = strchr(token, ':');
00236 
00237             //Separator found?
00238             if(separator != NULL)
00239             {
00240                //Split the line
00241                *separator = '\0';
00242 
00243                //Save user name
00244                strSafeCopy(connection->request.auth.user,
00245                   token, HTTP_SERVER_USERNAME_MAX_LEN);
00246 
00247                //Point to the password
00248                token = separator + 1;
00249                //Save password
00250                connection->request.auth.password = token;
00251             }
00252          }
00253       }
00254 
00255       //Debug message
00256       TRACE_DEBUG("Authorization header:\r\n");
00257       TRACE_DEBUG("  username: %s\r\n", connection->request.auth.user);
00258       TRACE_DEBUG("  password: %s\r\n", connection->request.auth.password);
00259    }
00260 #endif
00261 #if (HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
00262    //Digest access authentication?
00263    else if(!strcasecmp(token, "Digest"))
00264    {
00265       size_t n;
00266       char_t *separator;
00267       char_t *name;
00268 
00269       //Use the relevant authentication scheme
00270       connection->request.auth.mode = HTTP_AUTH_MODE_DIGEST;
00271       //Get the first parameter
00272       token = strtok_r(NULL, ",", &p);
00273 
00274       //Parse the Authorization header field
00275       while(token != NULL)
00276       {
00277          //Check whether a separator is present
00278          separator = strchr(token, '=');
00279 
00280          //Separator found?
00281          if(separator != NULL)
00282          {
00283             //Split the string
00284             *separator = '\0';
00285 
00286             //Get field name and value
00287             name = strTrimWhitespace(token);
00288             value = strTrimWhitespace(separator + 1);
00289 
00290             //Retrieve the length of the value field
00291             n = strlen(value);
00292 
00293             //Discard the surrounding quotes
00294             if(n > 0 && value[n - 1] == '\"')
00295                value[n - 1] = '\0';
00296             if(value[0] == '\"')
00297                value++;
00298 
00299             //Check parameter name
00300             if(!strcasecmp(name, "username"))
00301             {
00302                //Save user name
00303                strSafeCopy(connection->request.auth.user,
00304                   value, HTTP_SERVER_USERNAME_MAX_LEN);
00305             }
00306             else if(!strcasecmp(name, "realm"))
00307             {
00308                //Save realm
00309                connection->request.auth.realm = value;
00310             }
00311             else if(!strcasecmp(name, "nonce"))
00312             {
00313                //Save nonce parameter
00314                connection->request.auth.nonce = value;
00315             }
00316             else if(!strcasecmp(name, "uri"))
00317             {
00318                //Save uri parameter
00319                connection->request.auth.uri = value;
00320             }
00321             else if(!strcasecmp(name, "qop"))
00322             {
00323                //Save qop parameter
00324                connection->request.auth.qop = value;
00325             }
00326             else if(!strcasecmp(name, "nc"))
00327             {
00328                //Save nc parameter
00329                connection->request.auth.nc = value;
00330             }
00331             else if(!strcasecmp(name, "cnonce"))
00332             {
00333                //Save cnonce parameter
00334                connection->request.auth.cnonce = value;
00335             }
00336             else if(!strcasecmp(name, "response"))
00337             {
00338                //Save response parameter
00339                connection->request.auth.response = value;
00340             }
00341             else if(!strcasecmp(name, "opaque"))
00342             {
00343                //Save opaque parameter
00344                connection->request.auth.opaque = value;
00345             }
00346 
00347             //Get next parameter
00348             token = strtok_r(NULL, ",", &p);
00349          }
00350       }
00351 
00352       //Debug message
00353       TRACE_DEBUG("Authorization header:\r\n");
00354       TRACE_DEBUG("  username: %s\r\n", connection->request.auth.user);
00355       TRACE_DEBUG("  realm: %s\r\n", connection->request.auth.realm);
00356       TRACE_DEBUG("  nonce: %s\r\n", connection->request.auth.nonce);
00357       TRACE_DEBUG("  uri: %s\r\n", connection->request.auth.uri);
00358       TRACE_DEBUG("  qop: %s\r\n", connection->request.auth.qop);
00359       TRACE_DEBUG("  nc: %s\r\n", connection->request.auth.nc);
00360       TRACE_DEBUG("  cnonce: %s\r\n", connection->request.auth.cnonce);
00361       TRACE_DEBUG("  response: %s\r\n", connection->request.auth.response);
00362       TRACE_DEBUG("  opaque: %s\r\n", connection->request.auth.opaque);
00363    }
00364 #endif
00365    else
00366    {
00367       //The specified authentication scheme is not supported
00368       return;
00369    }
00370 
00371 #if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED || HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
00372    //The Authorization header has been found
00373    connection->request.auth.found = TRUE;
00374 
00375    //Invoke user-defined callback, if any
00376    if(connection->settings->authCallback != NULL)
00377    {
00378       //Check whether the access to the specified URI is authorized
00379       connection->status = connection->settings->authCallback(connection,
00380          connection->request.auth.user, connection->request.uri);
00381    }
00382    else
00383    {
00384       //Access to the specified URI is allowed
00385       connection->status = HTTP_ACCESS_ALLOWED;
00386    }
00387 #endif
00388 }
00389 
00390 
00391 /**
00392  * @brief Format WWW-Authenticate header field
00393  * @param[in] connection Structure representing an HTTP connection
00394  * @param[out] output Buffer where to format the header field
00395  * @return Total length of the header field
00396  **/
00397 
00398 size_t httpAddAuthenticateField(HttpConnection *connection, char_t *output)
00399 {
00400    size_t n;
00401 
00402 #if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED)
00403    //Basic authentication scheme?
00404    if(connection->response.auth.mode == HTTP_AUTH_MODE_BASIC)
00405    {
00406       //Set WWW-Authenticate field
00407       n = sprintf(output, "WWW-Authenticate: Basic realm=\"Protected Area\"\r\n");
00408    }
00409    else
00410 #endif
00411 #if (HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
00412    //Digest authentication scheme?
00413    if(connection->response.auth.mode == HTTP_AUTH_MODE_DIGEST)
00414    {
00415       error_t error;
00416       size_t k;
00417       uint8_t opaque[16];
00418 
00419       //Set WWW-Authenticate field
00420       n = sprintf(output, "WWW-Authenticate: Digest\r\n");
00421       n += sprintf(output + n, "  realm=\"Protected Area\",\r\n");
00422       n += sprintf(output + n, "  qop=\"auth\",\r\n");
00423       n += sprintf(output + n, "  nonce=\"");
00424 
00425       //The nonce is a server-specified data string which should be uniquely
00426       //generated each time a 401 response is made
00427       error = httpGenerateNonce(connection->serverContext, output + n, &k);
00428       //Any error to report?
00429       if(error)
00430          return error;
00431 
00432       //Advance pointer
00433       n += k;
00434       //Properly terminate the nonce string
00435       n += sprintf(output + n, "\",\r\n");
00436 
00437       //Format opaque parameter
00438       n += sprintf(output + n, "  opaque=\"");
00439 
00440       //Generate a random value
00441       if(connection->settings->randCallback != NULL)
00442          error = connection->settings->randCallback(opaque, 16);
00443       else
00444          error = ERROR_FAILURE;
00445 
00446       //Random number generation failed?
00447       if(error)
00448          return error;
00449 
00450       //Convert the byte array to hex string
00451       httpConvertArrayToHexString(opaque, 16, output + n);
00452 
00453       //Advance pointer
00454       n += 32;
00455       //Properly terminate the opaque string
00456       n += sprintf(output + n, "\"");
00457 
00458       //The STALE flag indicates that the previous request from the client
00459       //was rejected because the nonce value was stale
00460       if(connection->response.auth.stale)
00461          n += sprintf(output + n, ",\r\n  stale=TRUE");
00462 
00463       //Properly terminate the WWW-Authenticate field
00464       n += sprintf(output + n, "\r\n");
00465    }
00466    else
00467 #endif
00468    //Unknown authentication scheme?
00469    {
00470       //No need to add the WWW-Authenticate header field
00471       n = 0;
00472    }
00473 
00474    //Return the total length of the WWW-Authenticate header field
00475    return n;
00476 }
00477 
00478 
00479 /**
00480  * @brief Nonce generation
00481  * @param[in] context Pointer to the HTTP server context
00482  * @param[in] output NULL-terminated string containing the nonce
00483  * @param[in] length NULL-terminated string containing the nonce count
00484  * @return Error code
00485  **/
00486 
00487 error_t httpGenerateNonce(HttpServerContext *context,
00488    char_t *output, size_t *length)
00489 {
00490 #if (HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
00491    error_t error;
00492    uint_t i;
00493    HttpNonceCacheEntry *entry;
00494    HttpNonceCacheEntry *oldestEntry;
00495    uint8_t nonce[HTTP_SERVER_NONCE_SIZE];
00496 
00497    //Acquire exclusive access to the nonce cache
00498    osAcquireMutex(&context->nonceCacheMutex);
00499 
00500    //Keep track of the oldest entry
00501    oldestEntry = &context->nonceCache[0];
00502 
00503    //Loop through nonce cache entries
00504    for(i = 0; i < HTTP_SERVER_NONCE_CACHE_SIZE; i++)
00505    {
00506       //Point to the current entry
00507       entry = &context->nonceCache[i];
00508 
00509       //Check whether the entry is currently in used or not
00510       if(!entry->count)
00511          break;
00512 
00513       //Keep track of the oldest entry in the table
00514       if(timeCompare(entry->timestamp, oldestEntry->timestamp) < 0)
00515          oldestEntry = entry;
00516    }
00517 
00518    //The oldest entry is removed whenever the table runs out of space
00519    if(i >= HTTP_SERVER_NONCE_CACHE_SIZE)
00520       entry = oldestEntry;
00521 
00522    //Generate a new nonce
00523    if(context->settings.randCallback != NULL)
00524       error = context->settings.randCallback(nonce, HTTP_SERVER_NONCE_SIZE);
00525    else
00526       error = ERROR_FAILURE;
00527 
00528    //Check status code
00529    if(!error)
00530    {
00531       //Convert the byte array to hex string
00532       httpConvertArrayToHexString(nonce, HTTP_SERVER_NONCE_SIZE, entry->nonce);
00533       //Clear nonce count
00534       entry->count = 1;
00535       //Save the time at which the nonce was generated
00536       entry->timestamp = osGetSystemTime();
00537 
00538       //Copy the nonce to the output buffer
00539       strcpy(output, entry->nonce);
00540       //Return the length of the nonce excluding the NULL character
00541       *length = HTTP_SERVER_NONCE_SIZE * 2;
00542    }
00543    else
00544    {
00545       //Random number generation failed
00546       memset(entry, 0, sizeof(HttpNonceCacheEntry));
00547    }
00548 
00549    //Release exclusive access to the nonce cache
00550    osReleaseMutex(&context->nonceCacheMutex);
00551    //Return status code
00552    return error;
00553 
00554 #else
00555    //Not implemented
00556    return ERROR_NOT_IMPLEMENTED;
00557 #endif
00558 }
00559 
00560 
00561 /**
00562  * @brief Nonce verification
00563  * @param[in] context Pointer to the HTTP server context
00564  * @param[in] nonce NULL-terminated string containing the nonce
00565  * @param[in] nc NULL-terminated string containing the nonce count
00566  * @return Error code
00567  **/
00568 
00569 error_t httpVerifyNonce(HttpServerContext *context,
00570    const char_t *nonce, const char_t *nc)
00571 {
00572 #if (HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
00573    error_t error;
00574    uint_t i;
00575    uint32_t count;
00576    systime_t time;
00577    HttpNonceCacheEntry *entry;
00578 
00579    //Check parameters
00580    if(nonce == NULL || nc == NULL)
00581       return ERROR_INVALID_PARAMETER;
00582 
00583    //Convert the nonce count to integer
00584    count = strtoul(nc, NULL, 16);
00585    //Get current time
00586    time = osGetSystemTime();
00587 
00588    //Acquire exclusive access to the nonce cache
00589    osAcquireMutex(&context->nonceCacheMutex);
00590 
00591    //Loop through nonce cache entries
00592    for(i = 0; i < HTTP_SERVER_NONCE_CACHE_SIZE; i++)
00593    {
00594       //Point to the current entry
00595       entry = &context->nonceCache[i];
00596 
00597       //Check nonce value
00598       if(!strcasecmp(entry->nonce, nonce))
00599       {
00600          //Make sure the nonce timestamp has not expired
00601          if((time - entry->timestamp) < HTTP_SERVER_NONCE_LIFETIME)
00602          {
00603             //Check nonce count to prevent replay attacks
00604             if(count >= entry->count)
00605             {
00606                //Update nonce count to the next expected value
00607                entry->count = count + 1;
00608                //We are done
00609                break;
00610             }
00611          }
00612       }
00613    }
00614 
00615    //Check whether the nonce is valid
00616    if(i < HTTP_SERVER_NONCE_CACHE_SIZE)
00617       error = NO_ERROR;
00618    else
00619       error = ERROR_NOT_FOUND;
00620 
00621    //Release exclusive access to the nonce cache
00622    osReleaseMutex(&context->nonceCacheMutex);
00623    //Return status code
00624    return error;
00625 
00626 #else
00627    //Not implemented
00628    return ERROR_NOT_IMPLEMENTED;
00629 #endif
00630 }
00631 
00632 #endif
00633