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.

main.cpp

Committer:
IanBenzMaxim
Date:
2016-08-15
Revision:
19:b8b0cd35f7b4
Parent:
17:41be4896ed6d
Child:
20:cdba71cb5506

File content as of revision 19:b8b0cd35f7b4:

/*******************************************************************************
* 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 "common.hpp"
#include "WebServerInterface.hpp"
#include "Factory.hpp"
#include "SensorNode.hpp"
#include "Masters/DS2465/DS2465.h"
#include "Display.hpp"
#include "RomId/RomId.h"
#include "ESP8266.hpp"
#include "mbed.h"

using OneWire::RomId;
using OneWire::DS2465;

/// Main status for the program.
enum Status
{
  InitializingController, ///< Configure DS2465 and connect to network.
  DisplaySessionId, ///< Display ID for use with website.
  SensorNodeNeedsDetection, ///< Prompt user to insert Sensor Node.
  DetectingSensorNode, ///< Check if Sensor Node present.
  SensorNodeNeedsProvision, ///< Sensor Node needs to be provisioned.
  ProvisioningSensorNode, ///< Provisioning Sensor Node to factory defaults.
  NormalOperation, ///< The normal demo operation state.
  SensorNodeNotAuthentic, ///< Sensor Node failed authentication check.
  ControllerInitializationError, ///< Failed to initialize Controller.
  ControllerHardwareError, ///< Controller hardware failed unexpectedly.
  SensorNodeHardwareError ///< Sensor Node hardware failed unexpectedly.
};

/// @{
/// Configuration options.
static const unsigned int webPostIntervalMs = 10000;
static const unsigned int webPostRetryIntervalMs = 1000;
static const uint8_t maxConsecutiveWebPostErrors = 3;
/// @}

/// @{
/// LCD display colors.
static const Display::Color Teal(0x00, 0xB2, 0xA9);
static const Display::Color Red(0xFF, 0x00, 0x00);
static const Display::Color Green(0x00, 0xFF, 0x00);
/// @}

/// @{
/// Peripheral and pin definitions
static Serial pc(USBTX, USBRX);
static DigitalIn provisionButton(D13);
static DigitalIn invalidateButton(D5);
static DigitalOut tempAlarmLed(D11, 1);
static DigitalOut filterLifeAlarmLed(D10, 1);
static I2C i2c(D14, D15);
static Display lcd(i2c, 0x78, 0x98);
static DS2465 ds2465(i2c, 0x30);
static SensorNode sensorNode(i2c, 0x90, 0x94, ds2465);
static Factory factory;
static ESP8266 esp8266(D1, D0, D2, D3, 38400);
static WebServerInterface webIntf(esp8266, &pc);
/// @}

static bool useInvalidSecret = false; ///< Imitate an invalid controller when posting to web server.
static unsigned int randomSeed = 0; ///< Create extra entropy for challenge.
static Status currentStatus = InitializingController;
static bool result = false;
static uint8_t consecutiveWebPostErrors = 0; ///< Account for a few network errors in case of flaky connection.
static Timer webPostTimer; ///< Software timer to track web posting interval.
static Timer retryTimer; ///< Software timer to track authentication retries.

static void blinkLeds(unsigned int time_ms); ///< Invert LEDs for a given amount of time.
static bool buttonPressed(DigitalIn & button); ///< Checks if button is pressed (returns true) and waits for release.
static void displayStatus(Status status); ///< Display status message on LCD.
static void displaySensorData(const SensorData & sensorData); ///< Display sensor data on the LCD.
static bool readWebSessionId(std::string & sessionId); ///< Read device's web session ID from it's nonvolatile storage.

#ifdef ASSEMBLY_TEST
#include "AssemblyTest.cpp"
#endif

int main()
{
  blinkLeds(500);
  
#ifdef ASSEMBLY_TEST
  assemblyTest();
#endif
  
  while (true)
  {
    Status nextStatus = currentStatus;
    switch (currentStatus)
    {
    case InitializingController:
      pc.baud(115200);
      i2c.frequency(100000);
      webPostTimer.start();
      
      // Set initial LCD state
      lcd.initialize();
      displayStatus(currentStatus);
      
      // Connect to Wifi network
      result = webIntf.initialize();
      
      // Read session ID
      if (result)
      {
        result = readWebSessionId(webIntf.sessionId);
      }
      
      // Provision DS2465 with master secret and page data
      if (result)
      {
        result = factory.provision(ds2465);
      }
      
      if (result)
      {
        nextStatus = DisplaySessionId;
      }
      else
      {
        nextStatus = ControllerInitializationError;
      }
      break;
      
    case DisplaySessionId:
      // Wait for user to press Provision button
      if (buttonPressed(provisionButton))
      {
        nextStatus = SensorNodeNeedsDetection;
      }
      break;
      
    case SensorNodeNeedsDetection:
      // Wait for user to press Provision button
      if (buttonPressed(provisionButton))
      {
        nextStatus = DetectingSensorNode;
      }
      break;
      
    case DetectingSensorNode:
      // Perform Sensor Node detection sequence
      switch (sensorNode.detect(randomSeed))
      {
      case SensorNode::UnableToCommunicate:
      default:
        nextStatus = SensorNodeHardwareError;
        break;
        
      case SensorNode::NotProvisioned:
        nextStatus = SensorNodeNeedsProvision;
        break;
        
      case SensorNode::NotAuthentic:
        nextStatus = SensorNodeNotAuthentic;
        break;
        
      case SensorNode::Authentic:
        nextStatus = NormalOperation;
        break;
      }
      break;
      
    case SensorNodeNeedsProvision:
      // Wait for user to press Provision button
      if (buttonPressed(provisionButton))
      {
        nextStatus = ProvisioningSensorNode;
      }
      break;
      
    case ProvisioningSensorNode:      
      if (!buttonPressed(invalidateButton)) // Provision normally
      {
        if (factory.provision(sensorNode, true))
        {
          nextStatus = NormalOperation;
        }
        else
        {
          nextStatus = SensorNodeNotAuthentic;
        }
      }
      else // Invalidate button also pressed; Load invalid secret
      {        
        // Provision with invalid secret
        if (factory.provision(sensorNode, false))
        {
          nextStatus = NormalOperation;
        }
        else
        {
          nextStatus = SensorNodeHardwareError;
        }
      }
      break;
      
    case NormalOperation:      
      // Check if user pressed Provision button
      if (buttonPressed(provisionButton))
      {
        // Re-provision Sensor Node
        nextStatus = ProvisioningSensorNode;
      }
      // Check if user pressed Invalidate button
      else if (buttonPressed(invalidateButton))
      {
        // Toggle between using valid and invalid secret
        // 1 blink = invalid; 2 blinks = valid
        useInvalidSecret = !useInvalidSecret;
        blinkLeds(100);
        if (!useInvalidSecret)
        {
          wait_ms(100);
          blinkLeds(100);
        }
      }
      // Check node and display measurements
      else
      {
        SensorData sensorData;
        // Read sensor data with authentication
        switch (sensorNode.authenticatedReadSensorData(randomSeed, sensorData))
        {
        case SensorNode::Authentic:
          // Update measurements on LCD
          displaySensorData(sensorData);
          
          // Update alarm LEDs
          tempAlarmLed = !sensorData.tempAlarm(); // Active Low
          filterLifeAlarmLed = !sensorData.filterLifeAlarm(); // Active Low
          
          // Send measurements to web if time interval reached
          if (webPostTimer.read_ms() >= webPostIntervalMs)
          {
            // Format, sign, and transmit data to web server
            result = webIntf.authPostHttpEvent(ds2465, SensorDataEvent, WebServerInterface::formatSensorDataPostBody(sensorData), !useInvalidSecret);
            if (result)
            {
              // Reset timer count after logging sample complete
              webPostTimer.reset();
              consecutiveWebPostErrors = 0;
            }
            // There was likely an error establishing a web connection
            else if (++consecutiveWebPostErrors < maxConsecutiveWebPostErrors)
            {
              // Wait and try again
              wait_ms(webPostRetryIntervalMs);
            }
            // Too many retry attempts
            else
            {
              // Assume we have lost network connection
              nextStatus = ControllerHardwareError;
            }
          }
          break;
          
        case SensorNode::NotAuthentic:
          nextStatus = SensorNodeNotAuthentic;
          break;
          
        case SensorNode::UnableToCommunicate:
        default:
          nextStatus = SensorNodeHardwareError;
          break;
        }
      }
      break;
      
    case SensorNodeNotAuthentic:
      // Wait for some time before retrying authentication
      retryTimer.reset();
      retryTimer.start();
      do
      {
        // Wait for user to press Provision button
        if (buttonPressed(provisionButton))
        {
          nextStatus = ProvisioningSensorNode;
          break;
        }
        // Try to authenticate and return to normal operation
        else if (webPostTimer.read_ms() >= webPostIntervalMs)
        {
          // Send event message to server
          result = webIntf.authPostHttpEvent(ds2465, InvalidSensorEvent, "", !useInvalidSecret);
          if (result)
          {
            // Reset timer count after logging complete
            webPostTimer.reset();
            consecutiveWebPostErrors = 0;
            
            // Try to authenticate again
            nextStatus = SensorNodeNeedsDetection;
          }
          else if (++consecutiveWebPostErrors < maxConsecutiveWebPostErrors)
          {
            // There was likely an error establishing a web connection
            // Wait and try again
            wait_ms(webPostRetryIntervalMs);
          }
          // Too many retry attempts
          else
          {
            // Assume we have lost network connection
            nextStatus = ControllerHardwareError;
            break;
          }
        }
      } while (retryTimer.read_ms() < webPostIntervalMs);
      retryTimer.stop();
      break;
      
    case ControllerInitializationError:
    case ControllerHardwareError:
    case SensorNodeHardwareError:
    default:
      // Do nothing until user resets
      break;
    }
    // Check if status changed
    if (currentStatus != nextStatus)
    {
      currentStatus = nextStatus;
      displayStatus(currentStatus); // Display status message on LCD
    }
    
    // Change seed value on every loop pass
    randomSeed++;
  }
}

/// Blink all LEDs for a certain amount of time.
/// @param time_ms Time in ms to blink for.
static void blinkLeds(unsigned int time_ms)
{
  tempAlarmLed = !tempAlarmLed;
  filterLifeAlarmLed = !filterLifeAlarmLed;
  wait_ms(time_ms);
  tempAlarmLed = !tempAlarmLed;
  filterLifeAlarmLed = !filterLifeAlarmLed;
}

/// Check if a button is pressed and wait for it to be release.
/// @param button Active low button to check.
/// @returns True if pressed.
static bool buttonPressed(DigitalIn & button)
{
  const int buttonPressed = 0; // Active low
  if (button == buttonPressed)
  {
    while (button == buttonPressed) ;
    return true;
  }
  // else
  return false;
}

/// Display the current status of the Controller on the LCD display.
static void displayStatus(Status status)
{
  switch (status)
  {
  case InitializingController:
    lcd.writeMessage("Initializing Controller...");
    lcd.setBackLightColor(Teal);
    break;
    
  case DisplaySessionId:
    lcd.writeLine("ID: " + webIntf.sessionId, Display::FirstLine);
    lcd.writeLine("Provision to begin", Display::SecondLine);
    lcd.setBackLightColor(Teal);
    break;
    
  case SensorNodeNeedsDetection:
    lcd.writeMessage("Insert Sensor Node and press Provision");
    lcd.setBackLightColor(Teal);
    break;
    
  case DetectingSensorNode:
    lcd.writeMessage("Detecting Sensor Node...");
    lcd.setBackLightColor(Teal);
    break;
    
  case SensorNodeNeedsProvision:
    lcd.writeMessage("Sensor Node Needs Provision");
    lcd.setBackLightColor(Teal);
    break;
    
  case ProvisioningSensorNode:
    lcd.writeMessage("Provisioning Sensor Node");
    lcd.setBackLightColor(Teal);
    break;
    
  case NormalOperation:
    // Everything handled in displaySensorData()
    break;
    
  case SensorNodeNotAuthentic:
    lcd.writeMessage("Sensor Node Not Authentic");
    lcd.setBackLightColor(Red);
    break;
    
  case ControllerInitializationError:
    lcd.writeMessage("Initialization Error Check Wi-Fi");
    lcd.setBackLightColor(Red);
    break;
    
  case ControllerHardwareError:
    lcd.writeMessage("Controller Hardware Error: Check Wi-Fi");
    lcd.setBackLightColor(Red);
    break;
    
  case SensorNodeHardwareError:
    lcd.writeMessage("Sensor Node Hardware Error");
    lcd.setBackLightColor(Red);
    break;
  }
}

/// Display sensor data on the LCD display during normal operation.
static void displaySensorData(const SensorData & sensorData)
{ 
  std::ostringstream stream;
  stream << "Chiller Temp: " << (int)sensorData.temp << "C";
  lcd.writeCompleteLine(stream.str(), Display::FirstLine);
  stream.str(""); // Clear stream
  stream << "Filter Life: " << (unsigned int)sensorData.filterLife << "%";
  lcd.writeCompleteLine(stream.str(), Display::SecondLine);
  lcd.setBackLightColor((sensorData.tempAlarm() || sensorData.filterLifeAlarm()) ? Red : Green);
}

/// Read the Session ID to use with the web server from ROM.
/// @note Session ID is taken from the ROM ID of the MAX66242.
/// @param[out] Session ID string.
/// @returns True on success.
static bool readWebSessionId(std::string & sessionId)
{  
  const uint8_t I2C_address = 0x32;
  const uint8_t ROM_address = 0x68;
  RomId romId;
  
  // Set register pointer
  if (i2c.write(I2C_address, reinterpret_cast<const char *>(&ROM_address), 1) != 0)
    return false;
  // Read ROM ID
  if (i2c.read(I2C_address, reinterpret_cast<char *>(&(static_cast<RomId::ByteBuffer &>(romId))), RomId::byteLen) != 0)
    return false;
  // Check if CRC valid
  if (!romId.crc8Valid())
    return false;
  sessionId = byteArrayToHexString(romId, RomId::byteLen);
  return true;
}