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.

WebServerInterface.cpp

Committer:
IanBenzMaxim
Date:
2017-01-26
Revision:
28:e5cdaf13d299
Parent:
27:81a87d29bedd
Child:
29:590a7561318b

File content as of revision 28:e5cdaf13d299:

/*******************************************************************************
* 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 <sstream>
#include <vector>
#include "WebServerInterface.hpp"
#include "NetworkStack.h"
#include "TCPSocket.h"
#include "Slaves/Authenticators/ISha256MacCoproc.h"
#include "SensorData.hpp"
#include "HexConversions.hpp"
#include "Serial.h"
#include "wait_api.h"

using OneWire::ISha256MacCoproc;

const char WebServerInterface::serverAddress[] = "www.mxim-security.us";
const unsigned int WebServerInterface::serverPort = 80;
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 size_t challengeLen = 32;
static const uint8_t defaultPaddingByte = 0x00;

/// Select the Transport Secret for the web server in the Controller.
/// @returns True on success.
static bool setHttpPostSecret(ISha256MacCoproc & MacCoproc, const OneWire::RomId & sessionId)
{
  ISha256MacCoproc::DevicePage fillData;
  fillData.fill(defaultPaddingByte);
  ISha256MacCoproc::SlaveSecretData secretData;
  secretData.fill(defaultPaddingByte);
  std::copy(sessionId.buffer.begin(), sessionId.buffer.end(), secretData.begin());
  return (MacCoproc.computeSlaveSecret(fillData, fillData, secretData) == ISha256MacCoproc::Success);
}

/// 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 ISha256MacCoproc & macCoproc, const uint8_t * input, size_t ilen, ISha256MacCoproc::Mac & output)
{
  ISha256MacCoproc::DeviceScratchpad block;
  size_t index = 0;
  ISha256MacCoproc::AuthMacData padding;
  padding.fill(defaultPaddingByte);
  output.fill(defaultPaddingByte); // Set initial hash value
  while (index < ilen)
  {
    if ((index + block.size()) <= ilen) // Full block
    {
      std::memcpy(block.data(), &input[index], block.size());
      index += block.size();
    }
    else // Partial block with padding
    {
      std::memcpy(block.data(), &input[index], ilen - index);
      std::memset(&block[ilen - index], defaultPaddingByte, block.size() - (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 ISha256MacCoproc & macCoproc, PostEvent event, const std::string & initialPostBody,
                                  const uint8_t (&challenge)[challengeLen])
{
  const 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<uint8_t> hashInput;
  hashInput.reserve(challengeLen + httpPost.length());
  hashInput.assign(challenge, challenge + challengeLen);
  hashInput.insert(hashInput.end(), httpPost.begin(), httpPost.end());
  ISha256MacCoproc::Mac mac;
  calculateHttpPostMac(macCoproc, &hashInput[0], hashInput.size(), mac);
  
  char contentLen[5];
  snprintf(contentLen, sizeof(contentLen) / sizeof(contentLen[0]), "%u", (hashInput.size() - challengeLen) + (mac.size() * 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.data(), mac.size(), httpPost);
  httpPost += newline;
  
  return httpPost;
}

bool WebServerInterface::authPostHttpEvent(ISha256MacCoproc & macCoproc, PostEvent event, const std::string & postData, bool setSecret)
{
  bool result;
  
  if (setSecret)
  {
    result = setHttpPostSecret(macCoproc, m_sessionId);
    if (!result)
      return result;
  }
  
  // Open connection
  TCPSocket socket(&networkStack);
  result = (socket.connect(serverAddress, 80) == 0);
  if (result)
  {
    // Request challenge
    std::string httpData = formatHttpGet(serverAddress, serverChallengePath, m_sessionIdString);
    result = (socket.send(httpData.data(), httpData.size()) == httpData.size());
    if (result)
    {
      // Receive server response
      int recvResult = socket.recv(recvBuf, sizeof(recvBuf) / sizeof(recvBuf[0]));
      result = recvResult > 0;

      if (result)
      {          
        // Parse challenge from response
        const std::string challengeSearch(newline + newline);
        httpData.assign(recvBuf, recvResult);
        size_t challengePos = httpData.find(challengeSearch);
        if ((challengePos != std::string::npos) && ((challengePos + challengeSearch.length() + (challengeLen * charsPerByte)) <= httpData.length()))
        {
          uint8_t challenge[challengeLen];
          challengePos += challengeSearch.length();
          for (size_t i = 0; i < challengeLen; i++)
          {
            std::sscanf(httpData.substr(challengePos + (i * charsPerByte), charsPerByte).c_str(), "%2hhx", &challenge[i]);
          }
          
          // Post sensor data
          httpData = formatHttpPost(serverAddress, serverPostPath, m_sessionIdString, macCoproc, event, postData, challenge);
          result = (socket.send(httpData.data(), httpData.size()) == httpData.size());
          if (result)
          {
              result = (socket.recv(recvBuf, sizeof(recvBuf) / sizeof(recvBuf[0])) >= 0);
          }
        }
      }
    }
    
    // Close connection
    socket.close();
  }
  
  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();
}

void WebServerInterface::setSessionId(const OneWire::RomId & sessionId)
{
    m_sessionIdString = byteArrayToHexString(sessionId.buffer.data(), sessionId.buffer.size());
    m_sessionId = sessionId;
}