Webserver+3d print
Diff: cyclone_tcp/web_socket/web_socket_auth.c
- Revision:
- 0:8918a71cdbe9
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cyclone_tcp/web_socket/web_socket_auth.c Sat Feb 04 18:15:49 2017 +0000 @@ -0,0 +1,335 @@ +/** + * @file web_socket_auth.c + * @brief HTTP authentication for WebSockets + * + * @section License + * + * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved. + * + * This file is part of CycloneTCP Open. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * @author Oryx Embedded SARL (www.oryx-embedded.com) + * @version 1.7.6 + **/ + +//Switch to the appropriate trace level +#define TRACE_LEVEL WEB_SOCKET_TRACE_LEVEL + +//Dependencies +#include <stdlib.h> +#include "core/net.h" +#include "web_socket/web_socket.h" +#include "web_socket/web_socket_auth.h" +#include "base64.h" +#include "md5.h" +#include "str.h" +#include "debug.h" + +//Check TCP/IP stack configuration +#if (WEB_SOCKET_SUPPORT == ENABLED) + + +/** + * @brief Parse WWW-Authenticate header field + * @param[in] webSocket Handle to a WebSocket + * @param[in] value NULL-terminated string that contains the value of header field + * @return Error code + **/ + +error_t webSocketParseAuthenticateField(WebSocket *webSocket, char_t *value) +{ +#if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED || WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED) + size_t n; + char_t *p; + char_t *token; + char_t *separator; + char_t *name; + WebSocketAuthContext *authContext; + + //Point to the handshake context + authContext = &webSocket->authContext; + + //Retrieve the authentication scheme + token = strtok_r(value, " \t", &p); + + //Any parsing error? + if(token == NULL) + return ERROR_INVALID_SYNTAX; + + //Basic access authentication? + if(!strcmp(token, "Basic")) + { + //Basic authentication is required by the WebSocket server + authContext->requiredAuthMode = WS_AUTH_MODE_BASIC; + } + //Digest access authentication? + else if(!strcasecmp(token, "Digest")) + { + //Digest authentication is required by the WebSocket server + authContext->requiredAuthMode = WS_AUTH_MODE_DIGEST; + } + //Unknown authentication scheme? + else + { + //Report an error + return ERROR_INVALID_SYNTAX; + } + + //Get the first parameter + token = strtok_r(NULL, ",", &p); + + //Parse the WWW-Authenticate field + while(token != NULL) + { + //Check whether a separator is present + separator = strchr(token, '='); + + //Separator found? + if(separator != NULL) + { + //Split the string + *separator = '\0'; + + //Get field name and value + name = strTrimWhitespace(token); + value = strTrimWhitespace(separator + 1); + + //Retrieve the length of the value field + n = strlen(value); + + //Discard the surrounding quotes + if(n > 0 && value[n - 1] == '\"') + value[n - 1] = '\0'; + if(value[0] == '\"') + value++; + + //Check parameter name + if(!strcasecmp(name, "realm")) + { + //Save realm + strSafeCopy(authContext->realm, value, WEB_SOCKET_REALM_MAX_LEN); + } +#if (WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED) + else if(!strcasecmp(name, "nonce")) + { + //Save nonce + strSafeCopy(authContext->nonce, value, WEB_SOCKET_NONCE_MAX_LEN + 1); + } + else if(!strcasecmp(name, "opaque")) + { + //Save nonce + strSafeCopy(authContext->opaque, value, WEB_SOCKET_OPAQUE_MAX_LEN + 1); + } + else if(!strcasecmp(name, "stale")) + { + //Save stale flag + if(!strcasecmp(value, "true")) + authContext->stale = TRUE; + else + authContext->stale = FALSE; + } +#endif + + //Get next parameter + token = strtok_r(NULL, ",", &p); + } + } +#endif + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Format Authorization header field + * @param[in] webSocket Handle to a WebSocket + * @param[out] output Buffer where to format the header field + * @return Total length of the header field + **/ + +size_t webSocketAddAuthorizationField(WebSocket *webSocket, char_t *output) +{ + size_t n; + +#if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED || WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED) + WebSocketAuthContext *authContext; + + //Point to the handshake context + authContext = &webSocket->authContext; +#endif + +#if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED) + //Basic authentication scheme? + if(authContext->selectedAuthMode == WS_AUTH_MODE_BASIC) + { + size_t k; + char_t *temp; + + //Temporary buffer + temp = (char_t *) webSocket->rxContext.buffer; + + //Format Authorization header field + n = sprintf(output, "Authorization: Basic "); + + //The client sends the userid and password, separated by a single colon + //character, within a Base64 encoded string in the credentials + k = sprintf(temp, "%s:%s", authContext->username, + authContext->password); + + //Encode the resulting string using Base64 + base64Encode(temp, k, output + n, &k); + //Update the total length of the header field + n += k; + + //Properly terminate the Authorization header field + n += sprintf(output + n, "\r\n"); + } + else +#endif +#if (WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED) + //Digest authentication scheme? + if(authContext->selectedAuthMode == WS_AUTH_MODE_DIGEST) + { + Md5Context md5Context; + char_t ha1[2 * MD5_DIGEST_SIZE + 1]; + char_t ha2[2 * MD5_DIGEST_SIZE + 1]; + char_t nc[9]; + + //Count of the number of requests (including the current request) + //that the client has sent with the nonce value in this request + authContext->nc++; + + //Convert the value to hex string + sprintf(nc, "%08x", authContext->nc); + + //Compute HA1 = MD5(username : realm : password) + md5Init(&md5Context); + md5Update(&md5Context, authContext->username, strlen(authContext->username)); + md5Update(&md5Context, ":", 1); + md5Update(&md5Context, authContext->realm, strlen(authContext->realm)); + md5Update(&md5Context, ":", 1); + md5Update(&md5Context, authContext->password, strlen(authContext->password)); + md5Final(&md5Context, NULL); + + //Convert MD5 hash to hex string + webSocketConvertArrayToHexString(md5Context.digest, MD5_DIGEST_SIZE, ha1); + //Debug message + TRACE_DEBUG(" HA1: %s\r\n", ha1); + + //Compute HA2 = MD5(method : uri) + md5Init(&md5Context); + md5Update(&md5Context, "GET", 3); + md5Update(&md5Context, ":", 1); + md5Update(&md5Context, webSocket->uri, strlen(webSocket->uri)); + md5Final(&md5Context, NULL); + + //Convert MD5 hash to hex string + webSocketConvertArrayToHexString(md5Context.digest, MD5_DIGEST_SIZE, ha2); + //Debug message + TRACE_DEBUG(" HA2: %s\r\n", ha2); + + //Compute MD5(HA1 : nonce : nc : cnonce : qop : HA1) + md5Init(&md5Context); + md5Update(&md5Context, ha1, strlen(ha1)); + md5Update(&md5Context, ":", 1); + md5Update(&md5Context, authContext->nonce, strlen(authContext->nonce)); + md5Update(&md5Context, ":", 1); + md5Update(&md5Context, nc, strlen(nc)); + md5Update(&md5Context, ":", 1); + md5Update(&md5Context, authContext->cnonce, strlen(authContext->cnonce)); + md5Update(&md5Context, ":", 1); + md5Update(&md5Context, "auth", 4); + md5Update(&md5Context, ":", 1); + md5Update(&md5Context, ha2, strlen(ha2)); + md5Final(&md5Context, NULL); + + //Convert MD5 hash to hex string + webSocketConvertArrayToHexString(md5Context.digest, MD5_DIGEST_SIZE, ha1); + //Debug message + TRACE_DEBUG(" response: %s\r\n", ha1); + + //Format Authorization header field + n = sprintf(output, "Authorization: Digest\r\n"); + + //Username + n += sprintf(output + n, " username=\"%s\",\r\n", authContext->username); + //Realm + n += sprintf(output + n, " realm=\"%s\",\r\n", authContext->realm); + //Nonce value + n += sprintf(output + n, " nonce=\"%s\",\r\n", authContext->nonce); + //URI + n += sprintf(output + n, " uri=\"%s\",\r\n", webSocket->uri); + //Quality of protection + n += sprintf(output + n, " qop=\"auth\",\r\n"); + //Nonce count + n += sprintf(output + n, " nc=\"%08x\",\r\n", authContext->nc); + //Cnonce value + n += sprintf(output + n, " cnonce=\"%s\",\r\n", authContext->cnonce); + //Response + n += sprintf(output + n, " response=\"%s\",\r\n", ha1); + //Opaque parameter + n += sprintf(output + n, " opaque=\"%s\",\r\n", authContext->opaque); + } + else +#endif + //Unknown authentication scheme? + { + //No need to add the Authorization header field + n = 0; + } + + //Return the total length of the Authorization header field + return n; +} + + +/** + * @brief Convert byte array to hex string + * @param[in] input Point to the byte array + * @param[in] inputLength Length of the byte array + * @param[out] output NULL-terminated string resulting from the conversion + * @return Error code + **/ + +void webSocketConvertArrayToHexString(const uint8_t *input, + size_t inputLength, char_t *output) +{ + size_t i; + + //Hex conversion table + static const char_t hexDigit[] = + { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + //Process byte array + for(i = 0; i < inputLength; i++) + { + //Convert upper nibble + output[i * 2] = hexDigit[(input[i] >> 4) & 0x0F]; + //Then convert lower nibble + output[i * 2 + 1] = hexDigit[input[i] & 0x0F]; + } + + //Properly terminate the string with a NULL character + output[i * 2] = '\0'; +} + +#endif +