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:
2017-11-06
Revision:
32:0a09505a656d
Parent:
29:590a7561318b

File content as of revision 32:0a09505a656d:

/*******************************************************************************
* 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 <mbed.h>
#include <MaximInterface/Devices/DS2465.hpp>
#include <MaximInterface/Platforms/mbed/I2CMaster.hpp>
#include <MaximInterface/Platforms/mbed/Sleep.hpp>
#include <MaximInterface/Utilities/RomId.hpp>
#include "SensorData.hpp"
#include "WebServerInterface.hpp"
#include "Factory.hpp"
#include "SensorNode.hpp"
#include "Display.hpp"
#include "ESP8266.hpp"

using namespace MaximInterface;

/// 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 int webPostIntervalMs = 10000;
static const int webPostRetryIntervalMs = 1000;
static const int 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};
static const Display::Color Purple = {0x6E, 0x25, 0x85};
/// @}

/// @{
/// Peripheral and pin definitions
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 MaximInterface::mbed::I2CMaster i2cWrapper(i2c);
static DS2465 ds2465(MaximInterface::mbed::Sleep::instance(), i2cWrapper, 0x30);
static SensorNode sensorNode(i2c, 0x90, 0x94, ds2465);
static ESP8266 esp8266(D1, D0, D2, D3, 38400);
static WebServerInterface webIntf(esp8266);
/// @}

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

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

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

int main() {
  blinkLeds(500);

#ifdef ASSEMBLY_TEST
  assemblyTest();
#endif

  while (true) {
    Status nextStatus = currentStatus;
    switch (currentStatus) {
    case InitializingController:
      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) {
        RomId sessionId;
        result = readWebSessionId(sessionId);
        if (result) {
          webIntf.setSessionId(sessionId);
        }
      }

      // Provision DS2465 with master secret and page data
      if (result) {
        result = provisionCoprocessor(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 (provisionSensorNode(sensorNode, true)) {
          nextStatus = NormalOperation;
        } else {
          nextStatus = SensorNodeNotAuthentic;
        }
      } else { // Invalidate button also pressed; Load invalid secret
        // Provision with invalid secret
        if (provisionSensorNode(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(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.sessionIdString(), 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(Purple);
    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: " << static_cast<int>(sensorData.temp) << "C";
  lcd.writeCompleteLine(stream.str(), Display::FirstLine);
  stream.str(""); // Clear stream
  stream << "Filter Life: " << static_cast<unsigned int>(sensorData.filterLife)
         << "%";
  lcd.writeCompleteLine(stream.str(), Display::SecondLine);
  lcd.setBackLightColor(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(RomId & 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 *>(romId.data()),
               romId.size()) != 0)
    return false;
  // Check if CRC valid
  if (!valid(romId))
    return false;
  sessionId = romId;
  return true;
}