Sergey Pastor / 1

Dependents:   Nucleo

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers web_socket_auth.c Source File

web_socket_auth.c

Go to the documentation of this file.
00001 /**
00002  * @file web_socket_auth.c
00003  * @brief HTTP authentication for WebSockets
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 WEB_SOCKET_TRACE_LEVEL
00031 
00032 //Dependencies
00033 #include <stdlib.h>
00034 #include "core/net.h"
00035 #include "web_socket/web_socket.h"
00036 #include "web_socket/web_socket_auth.h"
00037 #include "base64.h"
00038 #include "md5.h"
00039 #include "str.h"
00040 #include "debug.h"
00041 
00042 //Check TCP/IP stack configuration
00043 #if (WEB_SOCKET_SUPPORT == ENABLED)
00044 
00045 
00046 /**
00047  * @brief Parse WWW-Authenticate header field
00048  * @param[in] webSocket Handle to a WebSocket
00049  * @param[in] value NULL-terminated string that contains the value of header field
00050  * @return Error code
00051  **/
00052 
00053 error_t webSocketParseAuthenticateField(WebSocket *webSocket, char_t *value)
00054 {
00055 #if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED || WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED)
00056    size_t n;
00057    char_t *p;
00058    char_t *token;
00059    char_t *separator;
00060    char_t *name;
00061    WebSocketAuthContext *authContext;
00062 
00063    //Point to the handshake context
00064    authContext = &webSocket->authContext;
00065 
00066    //Retrieve the authentication scheme
00067    token = strtok_r(value, " \t", &p);
00068 
00069    //Any parsing error?
00070    if(token == NULL)
00071       return ERROR_INVALID_SYNTAX;
00072 
00073    //Basic access authentication?
00074    if(!strcmp(token, "Basic"))
00075    {
00076       //Basic authentication is required by the WebSocket server
00077       authContext->requiredAuthMode = WS_AUTH_MODE_BASIC;
00078    }
00079    //Digest access authentication?
00080    else if(!strcasecmp(token, "Digest"))
00081    {
00082       //Digest authentication is required by the WebSocket server
00083       authContext->requiredAuthMode = WS_AUTH_MODE_DIGEST;
00084    }
00085    //Unknown authentication scheme?
00086    else
00087    {
00088       //Report an error
00089       return ERROR_INVALID_SYNTAX;
00090    }
00091 
00092    //Get the first parameter
00093    token = strtok_r(NULL, ",", &p);
00094 
00095    //Parse the WWW-Authenticate field
00096    while(token != NULL)
00097    {
00098       //Check whether a separator is present
00099       separator = strchr(token, '=');
00100 
00101       //Separator found?
00102       if(separator != NULL)
00103       {
00104          //Split the string
00105          *separator = '\0';
00106 
00107          //Get field name and value
00108          name = strTrimWhitespace(token);
00109          value = strTrimWhitespace(separator + 1);
00110 
00111          //Retrieve the length of the value field
00112          n = strlen(value);
00113 
00114          //Discard the surrounding quotes
00115          if(n > 0 && value[n - 1] == '\"')
00116             value[n - 1] = '\0';
00117          if(value[0] == '\"')
00118             value++;
00119 
00120          //Check parameter name
00121          if(!strcasecmp(name, "realm"))
00122          {
00123             //Save realm
00124             strSafeCopy(authContext->realm, value, WEB_SOCKET_REALM_MAX_LEN);
00125          }
00126 #if (WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED)
00127          else if(!strcasecmp(name, "nonce"))
00128          {
00129             //Save nonce
00130             strSafeCopy(authContext->nonce, value, WEB_SOCKET_NONCE_MAX_LEN + 1);
00131          }
00132          else if(!strcasecmp(name, "opaque"))
00133          {
00134             //Save nonce
00135             strSafeCopy(authContext->opaque, value, WEB_SOCKET_OPAQUE_MAX_LEN + 1);
00136          }
00137          else if(!strcasecmp(name, "stale"))
00138          {
00139             //Save stale flag
00140             if(!strcasecmp(value, "true"))
00141                authContext->stale = TRUE;
00142             else
00143                authContext->stale = FALSE;
00144          }
00145 #endif
00146 
00147          //Get next parameter
00148          token = strtok_r(NULL, ",", &p);
00149       }
00150    }
00151 #endif
00152 
00153    //Successful processing
00154    return NO_ERROR;
00155 }
00156 
00157 
00158 /**
00159  * @brief Format Authorization header field
00160  * @param[in] webSocket Handle to a WebSocket
00161  * @param[out] output Buffer where to format the header field
00162  * @return Total length of the header field
00163  **/
00164 
00165 size_t webSocketAddAuthorizationField(WebSocket *webSocket, char_t *output)
00166 {
00167    size_t n;
00168 
00169 #if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED || WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED)
00170    WebSocketAuthContext *authContext;
00171 
00172    //Point to the handshake context
00173    authContext = &webSocket->authContext;
00174 #endif
00175 
00176 #if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED)
00177    //Basic authentication scheme?
00178    if(authContext->selectedAuthMode == WS_AUTH_MODE_BASIC)
00179    {
00180       size_t k;
00181       char_t *temp;
00182 
00183       //Temporary buffer
00184       temp = (char_t *) webSocket->rxContext.buffer;
00185 
00186       //Format Authorization header field
00187       n = sprintf(output, "Authorization: Basic ");
00188 
00189       //The client sends the userid and password, separated by a single colon
00190       //character, within a Base64 encoded string in the credentials
00191       k = sprintf(temp, "%s:%s", authContext->username,
00192          authContext->password);
00193 
00194       //Encode the resulting string using Base64
00195       base64Encode(temp, k, output + n, &k);
00196       //Update the total length of the header field
00197       n += k;
00198 
00199       //Properly terminate the Authorization header field
00200       n += sprintf(output + n, "\r\n");
00201    }
00202    else
00203 #endif
00204 #if (WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED)
00205    //Digest authentication scheme?
00206    if(authContext->selectedAuthMode == WS_AUTH_MODE_DIGEST)
00207    {
00208       Md5Context md5Context;
00209       char_t ha1[2 * MD5_DIGEST_SIZE + 1];
00210       char_t ha2[2 * MD5_DIGEST_SIZE + 1];
00211       char_t nc[9];
00212 
00213       //Count of the number of requests (including the current request)
00214       //that the client has sent with the nonce value in this request
00215       authContext->nc++;
00216 
00217       //Convert the value to hex string
00218       sprintf(nc, "%08x", authContext->nc);
00219 
00220       //Compute HA1 = MD5(username : realm : password)
00221       md5Init(&md5Context);
00222       md5Update(&md5Context, authContext->username, strlen(authContext->username));
00223       md5Update(&md5Context, ":", 1);
00224       md5Update(&md5Context, authContext->realm, strlen(authContext->realm));
00225       md5Update(&md5Context, ":", 1);
00226       md5Update(&md5Context, authContext->password, strlen(authContext->password));
00227       md5Final(&md5Context, NULL);
00228 
00229       //Convert MD5 hash to hex string
00230       webSocketConvertArrayToHexString(md5Context.digest, MD5_DIGEST_SIZE, ha1);
00231       //Debug message
00232       TRACE_DEBUG("  HA1: %s\r\n", ha1);
00233 
00234       //Compute HA2 = MD5(method : uri)
00235       md5Init(&md5Context);
00236       md5Update(&md5Context, "GET", 3);
00237       md5Update(&md5Context, ":", 1);
00238       md5Update(&md5Context, webSocket->uri, strlen(webSocket->uri));
00239       md5Final(&md5Context, NULL);
00240 
00241       //Convert MD5 hash to hex string
00242       webSocketConvertArrayToHexString(md5Context.digest, MD5_DIGEST_SIZE, ha2);
00243       //Debug message
00244       TRACE_DEBUG("  HA2: %s\r\n", ha2);
00245 
00246       //Compute MD5(HA1 : nonce : nc : cnonce : qop : HA1)
00247       md5Init(&md5Context);
00248       md5Update(&md5Context, ha1, strlen(ha1));
00249       md5Update(&md5Context, ":", 1);
00250       md5Update(&md5Context, authContext->nonce, strlen(authContext->nonce));
00251       md5Update(&md5Context, ":", 1);
00252       md5Update(&md5Context, nc, strlen(nc));
00253       md5Update(&md5Context, ":", 1);
00254       md5Update(&md5Context, authContext->cnonce, strlen(authContext->cnonce));
00255       md5Update(&md5Context, ":", 1);
00256       md5Update(&md5Context, "auth", 4);
00257       md5Update(&md5Context, ":", 1);
00258       md5Update(&md5Context, ha2, strlen(ha2));
00259       md5Final(&md5Context, NULL);
00260 
00261       //Convert MD5 hash to hex string
00262       webSocketConvertArrayToHexString(md5Context.digest, MD5_DIGEST_SIZE, ha1);
00263       //Debug message
00264       TRACE_DEBUG("  response: %s\r\n", ha1);
00265 
00266       //Format Authorization header field
00267       n = sprintf(output, "Authorization: Digest\r\n");
00268 
00269       //Username
00270       n += sprintf(output + n, "  username=\"%s\",\r\n", authContext->username);
00271       //Realm
00272       n += sprintf(output + n, "  realm=\"%s\",\r\n", authContext->realm);
00273       //Nonce value
00274       n += sprintf(output + n, "  nonce=\"%s\",\r\n", authContext->nonce);
00275       //URI
00276       n += sprintf(output + n, "  uri=\"%s\",\r\n", webSocket->uri);
00277       //Quality of protection
00278       n += sprintf(output + n, "  qop=\"auth\",\r\n");
00279       //Nonce count
00280       n += sprintf(output + n, "  nc=\"%08x\",\r\n", authContext->nc);
00281       //Cnonce value
00282       n += sprintf(output + n, "  cnonce=\"%s\",\r\n", authContext->cnonce);
00283       //Response
00284       n += sprintf(output + n, "  response=\"%s\",\r\n", ha1);
00285       //Opaque parameter
00286       n += sprintf(output + n, "  opaque=\"%s\",\r\n", authContext->opaque);
00287    }
00288    else
00289 #endif
00290    //Unknown authentication scheme?
00291    {
00292       //No need to add the Authorization header field
00293       n = 0;
00294    }
00295 
00296    //Return the total length of the Authorization header field
00297    return n;
00298 }
00299 
00300 
00301 /**
00302  * @brief Convert byte array to hex string
00303  * @param[in] input Point to the byte array
00304  * @param[in] inputLength Length of the byte array
00305  * @param[out] output NULL-terminated string resulting from the conversion
00306  * @return Error code
00307  **/
00308 
00309 void webSocketConvertArrayToHexString(const uint8_t *input,
00310    size_t inputLength, char_t *output)
00311 {
00312    size_t i;
00313 
00314    //Hex conversion table
00315    static const char_t hexDigit[] =
00316    {
00317       '0', '1', '2', '3', '4', '5', '6', '7',
00318       '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
00319    };
00320 
00321    //Process byte array
00322    for(i = 0; i < inputLength; i++)
00323    {
00324       //Convert upper nibble
00325       output[i * 2] = hexDigit[(input[i] >> 4) & 0x0F];
00326       //Then convert lower nibble
00327       output[i * 2 + 1] = hexDigit[input[i] & 0x0F];
00328    }
00329 
00330    //Properly terminate the string with a NULL character
00331    output[i * 2] = '\0';
00332 }
00333 
00334 #endif
00335