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
diff -r 19b8608fc4ee -r e1c7c1c636af WebServerInterface.cpp
--- /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
MAXREFDES143#: DeepCover Embedded Security in IoT Authenticated Sensing & Notification