MAXREFDES143#: DeepCover Embedded Security in IoT Authenticated Sensing & Notification
Dependencies: MaximInterface mbed
The MAXREFDES143# is an Internet of Things (IoT) embedded security reference design, built to protect an industrial sensing node by means of authentication and notification to a web server. The hardware includes a peripheral module representing a protected sensor node monitoring operating temperature and remaining life of a filter (simulated through ambient light sensing) and an mbed shield representing a controller node responsible for monitoring one or more sensor nodes. The design is hierarchical with each controller node communicating data from connected sensor nodes to a web server that maintains a centralized log and dispatches notifications as necessary. The mbed shield contains a Wi-Fi module, a DS2465 coprocessor with 1-Wire® master function, an LCD, LEDs, and pushbuttons. The protected sensor node contains a DS28E15 authenticator, a DS7505 temperature sensor, and a MAX44009 light sensor. The mbed shield communicates to a web server by the onboard Wi-Fi module and to the protected sensor node with I2C and 1-Wire. The MAXREFDES143# is equipped with a standard shield connector for immediate testing using an mbed board such as the MAX32600MBED#. The simplicity of this design enables rapid integration into any star-topology IoT network requiring the heightened security with low overhead provided by the SHA-256 symmetric-key algorithm.
More information about the MAXREFDES143# is available on the Maxim Integrated website.
Diff: WebServerInterface.cpp
- Revision:
- 1:e1c7c1c636af
- Child:
- 3:ac723be395d9
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebServerInterface.cpp Thu Apr 14 19:48:01 2016 +0000 @@ -0,0 +1,331 @@ +/******************************************************************************* +* Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the "Software"), +* to deal in the Software without restriction, including without limitation +* the rights to use, copy, modify, merge, publish, distribute, sublicense, +* and/or sell copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES +* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +* OTHER DEALINGS IN THE SOFTWARE. +* +* Except as contained in this notice, the name of Maxim Integrated +* Products, Inc. shall not be used except as stated in the Maxim Integrated +* Products, Inc. Branding Policy. +* +* The mere transfer of this software does not imply any licenses +* of trade secrets, proprietary technology, copyrights, patents, +* trademarks, maskwork rights, or any other form of intellectual +* property whatsoever. Maxim Integrated Products, Inc. retains all +* ownership rights. +******************************************************************************* +*/ + +#include <vector> + +#include "WebServerInterface.hpp" +#include "ESP8266.hpp" +#include "OneWire_Masters/ISha256MacCoprocessor.hpp" +#include "common.hpp" +#include "mbed.h" + +const char WebServerInterface::wifiSsid[] = "WifiSsid"; +const char WebServerInterface::wifiPassword[] = "WifiPassword"; +const char WebServerInterface::serverAddress[] = "website.com"; +const char WebServerInterface::serverPostPath[] = "/post.php"; +const char WebServerInterface::serverChallengePath[] = "/challenge.php"; + +// HTTP formatting constants +static const char keyValSeparator = '='; +static const char fieldSeparator = '&'; +static const std::string newline = "\r\n"; +static const char sessionIdKey[] = "SessionId"; + +// Authentication MAC constants +static const std::size_t challengeLen = 32; +static const std::uint8_t defaultPaddingByte = 0x00; + +/// Select the Transport Secret for the web server in the Controller. +/// @returns True on success. +static bool setHttpPostSecret(ISha256MacCoprocessor & MacCoproc) +{ + ISha256MacCoprocessor::DevicePage fillData; + std::memset(fillData, defaultPaddingByte, fillData.length); + return (MacCoproc.computeSlaveSecret(fillData, fillData, reinterpret_cast<ISha256MacCoprocessor::SlaveSecretData &>(fillData)) == ISha256MacCoprocessor::Success); +} + +WebServerInterface::WebServerInterface(ESP8266 & esp8266, mbed::Serial * pc) + : esp8266(esp8266), pc(pc) +{ + +} + +bool WebServerInterface::initialize() +{ + esp8266.setPowered(true); + esp8266.reset(); + bool result = (esp8266.performSelfTest() == ESP8266::AT_OK); + if (!result) + { + return false; + } + result = (esp8266.setCurrentWifiMode(ESP8266::softAP_station_mode) == ESP8266::AT_OK); + if (!result) + { + return false; + } + result = (esp8266.setMaxRFTXPower(10) == ESP8266::AT_OK); + if (!result) + { + return false; + } + result = (esp8266.joinCurrentAccessPoint(wifiSsid, wifiPassword) == ESP8266::AT_OK); + if (!result) + { + return false; + } + return true; +} + +/// Format an HTTP GET request as a string for transmission. +/// @param host Web server address. +/// @param path Web server location to retrieve. +/// @param sessionId Session ID used to identify this controller. +/// @returns GET request string. +static std::string formatHttpGet(const std::string & host, const std::string & path, const std::string & sessionId) +{ + std::ostringstream httpGetStream; + httpGetStream << "GET " << path; + if (sessionId.length() > 0) + httpGetStream << '?' << sessionIdKey << keyValSeparator << sessionId; + httpGetStream << " HTTP/1.1" << newline; + httpGetStream << "Host: " << host << newline; + httpGetStream << newline; + return httpGetStream.str(); +} + +/// Computes a MAC using the Transport Secret to sign HTTP POST requests. +/// @param macCoproc Coprocessor such as the DS2465 used to calculate the authentication MAC. +/// @param input Message array used for MAC calculation. +/// @param ilen Length of array input. +/// @param output Calculated MAC output. +static void calculateHttpPostMac(const ISha256MacCoprocessor & macCoproc, const std::uint8_t * input, std::size_t ilen, ISha256MacCoprocessor::Mac & output) +{ + ISha256MacCoprocessor::DeviceScratchpad block; + std::size_t index = 0; + ISha256MacCoprocessor::AuthMacData padding; + std::memset(padding, defaultPaddingByte, padding.length); + std::memset(output, defaultPaddingByte, output.length); // Set initial hash value + while (index < ilen) + { + if ((index + block.length) <= ilen) // Full block + { + std::memcpy(block, &input[index], block.length); + index += block.length; + } + else // Partial block with padding + { + std::memcpy(block, &input[index], ilen - index); + std::memset(&block[ilen - index], defaultPaddingByte, block.length - (ilen - index)); + index = ilen; + } + // Write data to coprocessor and hash block + macCoproc.computeAuthMac(output, block, padding, output); + } +} + +/// Format an HTTP POST request as a string for transmission. +/// @param host Web server address. +/// @param path Web server location to receive POST. +/// @param sessionId Session ID used to identify this Controller. +/// @param macCoproc Coprocessor such as the DS2465 used to calculate the authentication MAC. +/// @param event Event message type. +/// @param initialPostBody Message body as determined by the event message type. +/// @param challenge Challenge previously received from web server for use in authentication MAC. +/// @returns POST request string. +static std::string formatHttpPost(const std::string & host, const std::string & path, const std::string & sessionId, + const ISha256MacCoprocessor & macCoproc, PostEvent event, const std::string & initialPostBody, + const std::uint8_t (&challenge)[challengeLen]) +{ + const std::size_t headerReserve = 115, bodyReserve = 200; + + std::string httpPost; + httpPost.reserve(initialPostBody.length() + headerReserve + bodyReserve); + + // Add session ID to post body + if (sessionId.length() > 0) + { + httpPost += sessionIdKey; + httpPost += keyValSeparator; + httpPost += sessionId; + } + + // Add event to post body + std::string eventString; + switch (event) + { + case SensorDataEvent: + eventString = "SensorData"; + break; + + case InvalidSensorEvent: + eventString = "InvalidSensor"; + break; + } + if (eventString.length() > 0) + { + if (httpPost.length() > 0) + httpPost += fieldSeparator; + httpPost += "Event"; + httpPost += keyValSeparator; + httpPost += eventString; + } + + // Add initial post body + if (initialPostBody.length() > 0) + { + if (httpPost.length() > 0) + httpPost += fieldSeparator; + httpPost += initialPostBody; + } + + // Combine initial post body with initial secret and hash + std::vector<std::uint8_t> hashInput; + hashInput.reserve(challengeLen + httpPost.length()); + hashInput.insert(hashInput.end(), challenge, challenge + challengeLen); + hashInput.insert(hashInput.end(), httpPost.begin(), httpPost.end()); + ISha256MacCoprocessor::Mac mac; + calculateHttpPostMac(macCoproc, &hashInput[0], hashInput.size(), mac); + + char contentLen[5]; + std::snprintf(contentLen, sizeof(contentLen) / sizeof(char), "%u", (hashInput.size() - challengeLen) + (mac.length * charsPerByte) + 5 /* &MAC= */); + + // Construct full post request + httpPost = ""; + httpPost += "POST "; + httpPost += path; + httpPost += " HTTP/1.1"; + httpPost += newline; + httpPost += "Host: "; + httpPost += host; + httpPost += newline; + httpPost += "Accept: */*"; + httpPost += newline; + httpPost += "Content-Length: "; + httpPost += contentLen; + httpPost += newline; + httpPost += "Content-Type: application/x-www-form-urlencoded"; + httpPost += newline; + httpPost += newline; + // Add post body + httpPost.append(reinterpret_cast<char *>(&hashInput[challengeLen]), hashInput.size() - challengeLen); + // Convert hash to hex string and add to post body + httpPost += fieldSeparator; + httpPost += "MAC"; + httpPost += keyValSeparator; + byteArrayToHexString(mac, mac.length, httpPost); + httpPost += newline; + + return httpPost; +} + +bool WebServerInterface::authPostHttpEvent(ISha256MacCoprocessor & macCoproc, PostEvent event, const std::string & postData, bool setSecret) +{ + const std::string challengeSearch(newline + newline); + bool result; + std::uint8_t challenge[challengeLen]; + std::string response; + + std::memset(challenge, defaultPaddingByte, challengeLen); + response.reserve(300); + + if (setSecret) + { + result = setHttpPostSecret(macCoproc); + if (!result) + return result; + } + + // Open connection + esp8266.clearRecvData(); // Clear received data buffer + result = (esp8266.openConnection(ESP8266::TCP, serverAddress, 80) == ESP8266::AT_OK); + if (result) + { + // Request challenge + result = (esp8266.sendData(formatHttpGet(serverAddress, serverChallengePath, sessionId)) == ESP8266::AT_OK); + if (result) + { + // Receive server response + for (int i = 0; i < 10; i++) + { + while (esp8266.recvIpDataReadable()) + { + char read = esp8266.getcRecvIpData(); + if (pc != NULL) + pc->putc(read); + if (response.length() < response.capacity()) + { + response += read; + } + else + { + wait_ms(ESP8266::sendDataRecoveryTimeMs); // Wait for ESP8266 specified recovery time + goto close_get_connection; + } + } + wait_ms(100); + } + // Close connection + close_get_connection: + esp8266.closeConnection(); + + // Parse challenge from response + std::size_t challengePos = response.find(challengeSearch); + if ((challengePos != std::string::npos) && ((challengePos + challengeSearch.length() + (challengeLen * charsPerByte)) <= response.length())) + { + challengePos += challengeSearch.length(); + for (std::size_t i = 0; i < challengeLen; i++) + { + std::sscanf(response.substr(challengePos + (i * charsPerByte), charsPerByte).c_str(), "%2hhx", &challenge[i]); + } + } + + // Post sensor data + result = (esp8266.openConnection(ESP8266::TCP, serverAddress, 80) == ESP8266::AT_OK); + if (result) + { + result = (esp8266.sendData(formatHttpPost(serverAddress, serverPostPath, sessionId, macCoproc, event, postData, challenge)) == ESP8266::AT_OK); + wait_ms(ESP8266::sendDataRecoveryTimeMs); // Wait for ESP8266 specified recovery time + } + } + + // Close connection + esp8266.closeConnection(); + } + + return result; +} + +std::string WebServerInterface::formatSensorDataPostBody(const SensorData & sensorData) +{ + // Create initial post body string from input data + std::ostringstream postBodyStream; + postBodyStream << "Temp" << keyValSeparator << static_cast<int>(sensorData.temp); + postBodyStream << fieldSeparator; + postBodyStream << "FilterLife" << keyValSeparator << static_cast<unsigned>(sensorData.filterLife); + postBodyStream << fieldSeparator; + postBodyStream << "TempAlarm" << keyValSeparator << (sensorData.tempAlarm() ? "true" : "false"); + postBodyStream << fieldSeparator; + postBodyStream << "FilterLifeAlarm" << keyValSeparator << (sensorData.filterLifeAlarm() ? "true" : "false"); + return postBodyStream.str(); +} \ No newline at end of file