MAXREFDES143#: DeepCover Embedded Security in IoT Authenticated Sensing & Notification

Dependencies:   MaximInterface mbed

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers WebServerInterface.cpp Source File

WebServerInterface.cpp

00001 /*******************************************************************************
00002 * Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.
00003 *
00004 * Permission is hereby granted, free of charge, to any person obtaining a
00005 * copy of this software and associated documentation files (the "Software"),
00006 * to deal in the Software without restriction, including without limitation
00007 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
00008 * and/or sell copies of the Software, and to permit persons to whom the
00009 * Software is furnished to do so, subject to the following conditions:
00010 *
00011 * The above copyright notice and this permission notice shall be included
00012 * in all copies or substantial portions of the Software.
00013 *
00014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
00015 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
00016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
00017 * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
00018 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
00019 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
00020 * OTHER DEALINGS IN THE SOFTWARE.
00021 *
00022 * Except as contained in this notice, the name of Maxim Integrated
00023 * Products, Inc. shall not be used except as stated in the Maxim Integrated
00024 * Products, Inc. Branding Policy.
00025 *
00026 * The mere transfer of this software does not imply any licenses
00027 * of trade secrets, proprietary technology, copyrights, patents,
00028 * trademarks, maskwork rights, or any other form of intellectual
00029 * property whatsoever. Maxim Integrated Products, Inc. retains all
00030 * ownership rights.
00031 *******************************************************************************/
00032 
00033 #include <vector>
00034 #include <Serial.h>
00035 #include <wait_api.h>
00036 #include <MaximInterface/Devices/DS2465.hpp>
00037 #include <MaximInterface/Utilities/HexConversions.hpp>
00038 #include "WebServerInterface.hpp"
00039 #include "ESP8266.hpp"
00040 #include "SensorData.hpp"
00041 
00042 using namespace MaximInterface;
00043 
00044 const char WebServerInterface::wifiSsid[] = "WifiSsid";
00045 const char WebServerInterface::wifiPassword[] = "WifiPassword";
00046 const char WebServerInterface::serverAddress[] = "www.maxim-security.com";
00047 const unsigned int WebServerInterface::serverPort = 80;
00048 const char WebServerInterface::serverPostPath[] = "/maxrefdes143/post.php";
00049 const char WebServerInterface::serverChallengePath[] =
00050     "/maxrefdes143/challenge.php";
00051 
00052 // HTTP formatting constants
00053 static const char keyValSeparator = '=';
00054 static const char fieldSeparator = '&';
00055 static const std::string newline = "\r\n";
00056 static const char sessionIdKey[] = "SessionId";
00057 
00058 // Authentication MAC constants
00059 static const size_t challengeLen = 32;
00060 static const uint8_t defaultPaddingByte = 0x00;
00061 
00062 /// Select the Transport Secret for the web server in the Controller.
00063 /// @returns True on success.
00064 static bool setHttpPostSecret(DS2465 & macCoproc, const RomId & sessionId) {
00065   Sha256::SlaveSecretData data;
00066   data.fill(defaultPaddingByte);
00067   std::copy(sessionId.begin(), sessionId.end(), data.begin() + 64);
00068   return !macCoproc.computeSlaveSecret(data);
00069 }
00070 
00071 bool WebServerInterface::initialize() {
00072   esp8266.setPowered(true);
00073   esp8266.reset();
00074   bool result = (esp8266.performSelfTest() == ESP8266::AT_OK);
00075   if (result) {
00076     result = (esp8266.setCurrentWifiMode(ESP8266::softAP_station_mode) ==
00077               ESP8266::AT_OK);
00078   }
00079   if (result) {
00080     result = (esp8266.setMaxRFTXPower(10) == ESP8266::AT_OK);
00081   }
00082   if (result) {
00083     result = (esp8266.joinCurrentAccessPoint(wifiSsid, wifiPassword) ==
00084               ESP8266::AT_OK);
00085   }
00086   return result;
00087 }
00088 
00089 /// Format an HTTP GET request as a string for transmission.
00090 /// @param host Web server address.
00091 /// @param path Web server location to retrieve.
00092 /// @param sessionId Session ID used to identify this controller.
00093 /// @returns GET request string.
00094 static std::string formatHttpGet(const std::string & host,
00095                                  const std::string & path,
00096                                  const std::string & sessionId) {
00097   std::ostringstream httpGetStream;
00098   httpGetStream << "GET " << path;
00099   if (sessionId.length() > 0)
00100     httpGetStream << '?' << sessionIdKey << keyValSeparator << sessionId;
00101   httpGetStream << " HTTP/1.1" << newline;
00102   httpGetStream << "Host: " << host << newline;
00103   httpGetStream << newline;
00104   return httpGetStream.str();
00105 }
00106 
00107 /// Computes a MAC using the Transport Secret to sign HTTP POST requests.
00108 /// @param macCoproc
00109 /// Coprocessor such as the DS2465 used to calculate the authentication MAC.
00110 /// @param input Message array used for MAC calculation.
00111 /// @param ilen Length of array input.
00112 /// @param output Calculated MAC output.
00113 static void calculateHttpPostMac(const DS2465 & macCoproc,
00114                                  const uint8_t * input, size_t ilen,
00115                                  Sha256::Hash & output) {
00116   const size_t blockSize = 32;
00117   output.fill(defaultPaddingByte); // Set initial hash value
00118   Sha256::AuthMacData macData;
00119   macData.fill(defaultPaddingByte);
00120   while (ilen > 0) {
00121     Sha256::AuthMacData::iterator macDataBegin =
00122         std::copy(output.begin(), output.end(), macData.begin());
00123     if (ilen >= blockSize) // Full block
00124     {
00125       std::copy(input, input + blockSize, macDataBegin);
00126       input += blockSize;
00127       ilen -= blockSize;
00128     } else // Partial block with padding
00129     {
00130       macDataBegin = std::copy(input, input + ilen, macDataBegin);
00131       std::fill(macDataBegin, macDataBegin + (blockSize - ilen),
00132                 defaultPaddingByte);
00133       ilen = 0;
00134     }
00135     // Write data to coprocessor and hash block
00136     macCoproc.computeAuthMac(macData, output);
00137   }
00138 }
00139 
00140 /// Format an HTTP POST request as a string for transmission.
00141 /// @param host Web server address.
00142 /// @param path Web server location to receive POST.
00143 /// @param sessionId Session ID used to identify this Controller.
00144 /// @param macCoproc
00145 /// Coprocessor such as the DS2465 used to calculate the authentication MAC.
00146 /// @param event Event message type.
00147 /// @param initialPostBody Message body as determined by the event message type.
00148 /// @param challenge
00149 /// Challenge previously received from web server for use in authentication MAC.
00150 /// @returns POST request string.
00151 static std::string formatHttpPost(const std::string & host,
00152                                   const std::string & path,
00153                                   const std::string & sessionId,
00154                                   const DS2465 & macCoproc, PostEvent event,
00155                                   const std::string & initialPostBody,
00156                                   const uint8_t (&challenge)[challengeLen]) {
00157   const size_t headerReserve = 115, bodyReserve = 200;
00158 
00159   std::string httpPost;
00160   httpPost.reserve(initialPostBody.length() + headerReserve + bodyReserve);
00161 
00162   // Add session ID to post body
00163   if (sessionId.length() > 0) {
00164     httpPost += sessionIdKey;
00165     httpPost += keyValSeparator;
00166     httpPost += sessionId;
00167   }
00168 
00169   // Add event to post body
00170   std::string eventString;
00171   switch (event) {
00172   case SensorDataEvent:
00173     eventString = "SensorData";
00174     break;
00175 
00176   case InvalidSensorEvent:
00177     eventString = "InvalidSensor";
00178     break;
00179   }
00180   if (eventString.length() > 0) {
00181     if (httpPost.length() > 0)
00182       httpPost += fieldSeparator;
00183     httpPost += "Event";
00184     httpPost += keyValSeparator;
00185     httpPost += eventString;
00186   }
00187 
00188   // Add initial post body
00189   if (initialPostBody.length() > 0) {
00190     if (httpPost.length() > 0)
00191       httpPost += fieldSeparator;
00192     httpPost += initialPostBody;
00193   }
00194 
00195   // Combine initial post body with initial secret and hash
00196   std::vector<uint8_t> hashInput;
00197   hashInput.reserve(challengeLen + httpPost.length());
00198   hashInput.assign(challenge, challenge + challengeLen);
00199   hashInput.insert(hashInput.end(), httpPost.begin(), httpPost.end());
00200   Sha256::Hash mac;
00201   calculateHttpPostMac(macCoproc, &hashInput[0], hashInput.size(), mac);
00202 
00203   char contentLen[5];
00204   snprintf(contentLen, sizeof(contentLen) / sizeof(contentLen[0]), "%u",
00205            (hashInput.size() - challengeLen) + (mac.size() * 2) +
00206                5 /* &MAC= */);
00207 
00208   // Construct full post request
00209   httpPost = "";
00210   httpPost += "POST ";
00211   httpPost += path;
00212   httpPost += " HTTP/1.1";
00213   httpPost += newline;
00214   httpPost += "Host: ";
00215   httpPost += host;
00216   httpPost += newline;
00217   httpPost += "Accept: */*";
00218   httpPost += newline;
00219   httpPost += "Content-Length: ";
00220   httpPost += contentLen;
00221   httpPost += newline;
00222   httpPost += "Content-Type: application/x-www-form-urlencoded";
00223   httpPost += newline;
00224   httpPost += newline;
00225   // Add post body
00226   httpPost.append(reinterpret_cast<char *>(&hashInput[challengeLen]),
00227                   hashInput.size() - challengeLen);
00228   // Convert hash to hex string and add to post body
00229   httpPost += fieldSeparator;
00230   httpPost += "MAC";
00231   httpPost += keyValSeparator;
00232   httpPost += byteArrayToHexString(mac.data(), mac.size());
00233   httpPost += newline;
00234 
00235   return httpPost;
00236 }
00237 
00238 bool WebServerInterface::authPostHttpEvent(DS2465 & macCoproc, PostEvent event,
00239                                            const std::string & postData,
00240                                            bool setSecret) {
00241   const std::string challengeSearch(newline + newline);
00242   bool result;
00243   uint8_t challenge[challengeLen];
00244   std::string response;
00245 
00246   std::memset(challenge, defaultPaddingByte, challengeLen);
00247   response.reserve(300);
00248 
00249   if (setSecret) {
00250     result = setHttpPostSecret(macCoproc, m_sessionId);
00251     if (!result)
00252       return result;
00253   }
00254 
00255   // Open connection
00256   esp8266.clearRecvData(); // Clear received data buffer
00257   result = (esp8266.openConnection(ESP8266::TCP, serverAddress, 80) ==
00258             ESP8266::AT_OK);
00259   if (result) {
00260     // Request challenge
00261     result =
00262         (esp8266.sendData(formatHttpGet(serverAddress, serverChallengePath,
00263                                         m_sessionIdString)) == ESP8266::AT_OK);
00264     if (result) {
00265       // Receive server response
00266       for (int i = 0; i < 10; i++) {
00267         while (esp8266.recvIpDataReadable()) {
00268           char read = esp8266.getcRecvIpData();
00269           if (response.length() < response.capacity()) {
00270             response += read;
00271           } else {
00272             // Wait for ESP8266 specified recovery time
00273             wait_ms(ESP8266::sendDataRecoveryTimeMs);
00274             goto close_get_connection;
00275           }
00276         }
00277         wait_ms(100);
00278       }
00279       // Close connection
00280     close_get_connection:
00281       esp8266.closeConnection();
00282 
00283       // Parse challenge from response
00284       size_t challengePos = response.find(challengeSearch);
00285       const size_t challengeStringLen = challengeLen * 2;
00286       if ((challengePos != std::string::npos) &&
00287           ((challengePos + challengeSearch.length() + challengeStringLen) <=
00288            response.length())) {
00289         challengePos += challengeSearch.length();
00290         const std::vector<uint_least8_t> parsedChallenge = hexStringToByteArray(
00291             response.substr(challengePos, challengeStringLen));
00292         std::copy(parsedChallenge.begin(), parsedChallenge.end(), challenge);
00293       }
00294 
00295       // Post sensor data
00296       result = (esp8266.openConnection(ESP8266::TCP, serverAddress,
00297                                        serverPort) == ESP8266::AT_OK);
00298       if (result) {
00299         result =
00300             (esp8266.sendData(formatHttpPost(
00301                  serverAddress, serverPostPath, m_sessionIdString, macCoproc,
00302                  event, postData, challenge)) == ESP8266::AT_OK);
00303         wait_ms(
00304             ESP8266::
00305                 sendDataRecoveryTimeMs); // Wait for ESP8266 specified recovery time
00306       }
00307     }
00308 
00309     // Close connection
00310     esp8266.closeConnection();
00311   }
00312 
00313   return result;
00314 }
00315 
00316 std::string
00317 WebServerInterface::formatSensorDataPostBody(const SensorData & sensorData) {
00318   // Create initial post body string from input data
00319   std::ostringstream postBodyStream;
00320   postBodyStream << "Temp" << keyValSeparator
00321                  << static_cast<int>(sensorData.temp);
00322   postBodyStream << fieldSeparator;
00323   postBodyStream << "FilterLife" << keyValSeparator
00324                  << static_cast<unsigned>(sensorData.filterLife);
00325   postBodyStream << fieldSeparator;
00326   postBodyStream << "TempAlarm" << keyValSeparator
00327                  << (sensorData.tempAlarm() ? "true" : "false");
00328   postBodyStream << fieldSeparator;
00329   postBodyStream << "FilterLifeAlarm" << keyValSeparator
00330                  << (sensorData.filterLifeAlarm() ? "true" : "false");
00331   return postBodyStream.str();
00332 }
00333 
00334 void WebServerInterface::setSessionId(const RomId & sessionId) {
00335   m_sessionIdString = byteArrayToHexString(sessionId.data(), sessionId.size());
00336   m_sessionId = sessionId;
00337 }