
DeepCover Embedded Security in IoT: Public-key Secured Data Paths
Dependencies: MaximInterface
The MAXREFDES155# is an internet-of-things (IoT) embedded-security reference design, built to authenticate and control a sensing node using elliptic-curve-based public-key cryptography with control and notification from a web server.
The hardware includes an ARM® mbed™ shield and attached sensor endpoint. The shield contains a DS2476 DeepCover® ECDSA/SHA-2 coprocessor, Wifi communication, LCD push-button controls, and status LEDs. The sensor endpoint is attached to the shield using a 300mm cable and contains a DS28C36 DeepCover ECDSA/SHA-2 authenticator, IR-thermal sensor, and aiming laser for the IR sensor. The MAXREFDES155# is equipped with a standard Arduino® form-factor shield connector for immediate testing using an mbed board such as the MAX32600MBED#. The combination of these two devices represent an IoT device. Communication to the web server is accomplished with the shield Wifi circuitry. Communication from the shield to the attached sensor module is accomplished over I2C . The sensor module represents an IoT endpoint that generates small data with a requirement for message authenticity/integrity and secure on/off operational control.
The design is hierarchical with each mbed platform and shield communicating data from the sensor node to a web server that maintains a centralized log and dispatches notifications as necessary. The simplicity of this design enables rapid integration into any star-topology IoT network to provide security with the low overhead and cost provided by the ECDSA-P256 asymmetric-key and SHA-256 symmetric-key algorithms.
More information about the MAXREFDES155# is available on the Maxim Integrated website.
Revision 12:46c5974a565f, committed 2017-06-01
- Comitter:
- IanBenzMaxim
- Date:
- Thu Jun 01 14:21:58 2017 -0500
- Parent:
- 11:989eabe2a376
- Child:
- 13:6a6225690c2e
- Commit message:
- Added bidirectional challenge support.
Changed in this revision
NormalOperationWindow.cpp | Show annotated file Show diff for this revision Revisions of this file |
NormalOperationWindow.hpp | Show annotated file Show diff for this revision Revisions of this file |
--- a/NormalOperationWindow.cpp Wed May 03 16:08:59 2017 -0500 +++ b/NormalOperationWindow.cpp Thu Jun 01 14:21:58 2017 -0500 @@ -96,8 +96,22 @@ return commands; } +// Creates a new command challenge, and adds it to an existing JSON document. +static DS2476::CmdResult addCommandChallenge(rapidjson::Document & document, CommandChallenge & commandChallenge) +{ + DS2476::Buffer buffer; + DS2476::CmdResult result = coproc.readRng(commandChallenge.size(), buffer); + if (result == DS2476::Success) + { + std::copy(buffer.begin(), buffer.end(), commandChallenge.begin()); + document.AddMember("challenge", rapidjson::Value(byteArrayToHexString(commandChallenge.data(), + commandChallenge.size()).c_str(), document.GetAllocator()).Move(), document.GetAllocator()); + } + return result; +} + // Adds signature information to an existing JSON document. -static DS2476::CmdResult signData(bool validSignature, const std::vector<uint8_t> & challenge, rapidjson::Document & document) +static DS2476::CmdResult signData(bool validSignature, const ResponseChallenge & challenge, rapidjson::Document & document) { // Move contents of document to a new location and create an empty object in document. rapidjson::Value data(rapidjson::kObjectType); @@ -133,13 +147,28 @@ return result; } +// Finalizes a command response to the server by adding the next command challenge and signing the data. +static DS2476::CmdResult finalizeResponse(bool validSignature, const ResponseChallenge & responseChallenge, + rapidjson::Document & document, CommandChallenge & commandChallenge) +{ + DS2476::CmdResult result = addCommandChallenge(document, commandChallenge); + if (result == DS2476::Success) + { + result = signData(validSignature, responseChallenge, document); + } + return result; +} + // Parse and verify a signed JSON string. -static DS2476::CmdResult verifySignedData(const std::string & verifyData, rapidjson::Document & signedData) +template <typename VerifyDataIt> +static DS2476::CmdResult verifySignedData(VerifyDataIt verifyDataBegin, VerifyDataIt verifyDataEnd, + const CommandChallenge & commandChallenge, rapidjson::Document & signedData) { using rapidjson::Value; using std::string; // Parse string and validate object schema. + string verifyData(verifyDataBegin, verifyDataEnd); signedData.Parse(verifyData.c_str()); if (!(signedData.IsObject() && signedData.HasMember("data") && signedData.HasMember("signature"))) { @@ -179,7 +208,7 @@ } std::copy(parsedBytes.begin(), parsedBytes.end(), signatureBuffer.s.begin()); - // Compute hash of the data. + // Get data to hash. // Need to use string searching here since there isn't currently a way to access raw elements // in rapidjson, and creating another copy of the data might consume too much memory. const string rawDataSearch("\"data\":"); @@ -196,10 +225,16 @@ signedData.RemoveAllMembers(); return DS2476::AuthenticationError; } + verifyData.erase(rawDataEnd); + verifyData.erase(0, rawDataBegin); + // Add in command challenge to data that will be verified. + verifyData.append(commandChallenge.begin(), commandChallenge.end()); + + // Compute hash of the data. const string::size_type chunkSize = 64; - for (string::size_type i = rawDataBegin; i < rawDataEnd; i += chunkSize) + for (string::size_type i = 0; i < verifyData.size(); i += chunkSize) { - const string::size_type remainingLength = rawDataEnd - i; + const string::size_type remainingLength = verifyData.size() - i; DS2476::CmdResult result = coproc.computeMultiblockHash(i == 0, remainingLength < chunkSize, DS2476::Buffer(verifyData.c_str() + i, verifyData.c_str() + i + std::min(remainingLength, chunkSize))); if (result != DS2476::Success) @@ -263,7 +298,7 @@ } NormalOperationWindow::NormalOperationWindow(std::auto_ptr<TCPSocket> & socket) : - socket(socket) /* Move construct */, + socket(socket) /* Move construct */, sendChallenge(true), validSignature(true), lastSensorNodeState(Disconnected), lastObjectTemp(0), lastAmbientTemp(0) { assert(this->socket.get() != NULL); @@ -330,7 +365,7 @@ return sensorNodeState; } -NormalOperationWindow::Result NormalOperationWindow::sendStatus(const std::vector<uint8_t> & challenge) +NormalOperationWindow::Result NormalOperationWindow::sendStatus(const ResponseChallenge & responseChallenge) { rapidjson::MemoryPoolAllocator<> allocator(defaultChunkSize); rapidjson::Document document(rapidjson::kObjectType, &allocator); @@ -400,7 +435,7 @@ document.AddMember("certificate", certificate, document.GetAllocator()); // Sign data and transmit to server. - result = signData(validSignature, challenge, document); + result = finalizeResponse(validSignature, responseChallenge, document, commandChallenge); if (result != DS2476::Success) { if (windowManager() != NULL) @@ -415,12 +450,12 @@ return NoChange; } -NormalOperationWindow::Result NormalOperationWindow::sendObjectTemp(const std::vector<uint8_t> & challenge) +NormalOperationWindow::Result NormalOperationWindow::sendObjectTemp(const ResponseChallenge & responseChallenge) { rapidjson::MemoryPoolAllocator<> allocator(defaultChunkSize); rapidjson::Document document(rapidjson::kObjectType, &allocator); - // Read object temperatue and add to document. + // Read object temperature and add to document. double objectTemp; bool sensorResult = sensorNode.readTemp(SensorNode::ObjectTemp, objectTemp); if (!sensorResult) @@ -436,7 +471,7 @@ document.AddMember("objectTemp", objectTemp, document.GetAllocator()); // Sign data and transmit to server. - DS2476::CmdResult coprocResult = signData(validSignature, challenge, document); + DS2476::CmdResult coprocResult = finalizeResponse(validSignature, responseChallenge, document, commandChallenge); if (coprocResult != DS2476::Success) { if (windowManager() != NULL) @@ -453,12 +488,12 @@ return NoChange; } -NormalOperationWindow::Result NormalOperationWindow::sendAmbientTemp(const std::vector<uint8_t> & challenge) +NormalOperationWindow::Result NormalOperationWindow::sendAmbientTemp(const ResponseChallenge & responseChallenge) { rapidjson::MemoryPoolAllocator<> allocator(defaultChunkSize); rapidjson::Document document(rapidjson::kObjectType, &allocator); - // Read ambient temperatue and add to document. + // Read ambient temperature and add to document. double ambientTemp; bool sensorResult = sensorNode.readTemp(SensorNode::AmbientTemp, ambientTemp); if (!sensorResult) @@ -474,7 +509,7 @@ document.AddMember("ambientTemp", ambientTemp, document.GetAllocator()); // Sign data and transmit to server. - DS2476::CmdResult coprocResult = signData(validSignature, challenge, document); + DS2476::CmdResult coprocResult = finalizeResponse(validSignature, responseChallenge, document, commandChallenge); if (coprocResult != DS2476::Success) { if (windowManager() != NULL) @@ -510,7 +545,7 @@ rapidjson::MemoryPoolAllocator<> allocator(defaultChunkSize); rapidjson::Document data(&allocator); // Verify command signature. - DS2476::CmdResult verifySignedResult = verifySignedData(std::string(it->first, it->second), data); + DS2476::CmdResult verifySignedResult = verifySignedData(it->first, it->second, commandChallenge, data); switch (verifySignedResult) { case DS2476::Success: // Command passed authentication. @@ -523,13 +558,13 @@ if (command.IsString()) { // Parse challenge if included. - std::vector<uint8_t> challengeData; + ResponseChallenge responseChallenge; if (data.HasMember("challenge")) { const rapidjson::Value & challenge = data["challenge"]; if (challenge.IsString()) { - challengeData = + responseChallenge = hexStringToByteArray(std::string(challenge.GetString(), challenge.GetStringLength())); } } @@ -537,7 +572,7 @@ // Execute the command. if (command == "getStatus") { - Result result = sendStatus(challengeData); + Result result = sendStatus(responseChallenge); if (result != NoChange) return result; } @@ -545,7 +580,7 @@ { if ((lastSensorNodeState == ValidLaserDisabled) || (lastSensorNodeState == ValidLaserEnabled)) { - Result result = sendObjectTemp(challengeData); + Result result = sendObjectTemp(responseChallenge); if (result != NoChange) return result; invalidate(); @@ -555,7 +590,7 @@ { if ((lastSensorNodeState == ValidLaserDisabled) || (lastSensorNodeState == ValidLaserEnabled)) { - Result result = sendAmbientTemp(challengeData); + Result result = sendAmbientTemp(responseChallenge); if (result != NoChange) return result; invalidate(); @@ -710,24 +745,39 @@ invalidate(); } - // Process socket data. - int recvResult = socket->recv(recvBuf, sizeof(recvBuf) / sizeof(recvBuf[0])); - if (recvResult > 0) + // Send challenge on first connection. + if (sendChallenge) { - std::printf("%*s\n", recvResult, recvBuf); - Result result = processReceivedData(recvResult); - if (result != NoChange) - return; + rapidjson::MemoryPoolAllocator<> allocator(defaultChunkSize); + rapidjson::Document document(rapidjson::kObjectType, &allocator); + DS2476::CmdResult result = addCommandChallenge(document, commandChallenge); + if (result == DS2476::Success) + { + sendJson(document, *socket); + sendChallenge = false; + } } - else if (recvResult != NSAPI_ERROR_WOULD_BLOCK) + // Process socket data. + else { - if (windowManager() != NULL) + int recvResult = socket->recv(recvBuf, sizeof(recvBuf) / sizeof(recvBuf[0])); + if (recvResult > 0) { - windowManager()->pop(); - std::auto_ptr<Window> window(new ErrorWindow("Socket receive failed")); - windowManager()->push(window); + std::printf("%*s\n", recvResult, recvBuf); + Result result = processReceivedData(recvResult); + if (result != NoChange) + return; } - return; + else if (recvResult != NSAPI_ERROR_WOULD_BLOCK) + { + if (windowManager() != NULL) + { + windowManager()->pop(); + std::auto_ptr<Window> window(new ErrorWindow("Socket receive failed")); + windowManager()->push(window); + } + return; + } } }
--- a/NormalOperationWindow.hpp Wed May 03 16:08:59 2017 -0500 +++ b/NormalOperationWindow.hpp Thu Jun 01 14:21:58 2017 -0500 @@ -36,9 +36,16 @@ #include <memory> #include <vector> #include <TCPSocket.h> +#include <array.h> #include "Button.hpp" #include "Window.hpp" +/// Challenge received from the server with a command to sign the response against. +typedef std::vector<uint8_t> ResponseChallenge; + +/// Challenge sent to the server with a command response to sign the next command against. +typedef OneWire::array<uint8_t, 32> CommandChallenge; + /// Handles normal operation of the demo including displaying status information and processing /// commands from the server. class NormalOperationWindow : public Window @@ -72,6 +79,8 @@ std::auto_ptr<TCPSocket> socket; char recvBuf[1280]; // Socket receive buffer. Must be large enough to hold the largest command. + CommandChallenge commandChallenge; + bool sendChallenge; // Device status information. bool validSignature; @@ -91,9 +100,9 @@ static SensorNodeState detectSensorNode(); Result processReceivedData(size_t recvBufSize); - Result sendStatus(const std::vector<uint8_t> & challenge); - Result sendObjectTemp(const std::vector<uint8_t> & challenge); - Result sendAmbientTemp(const std::vector<uint8_t> & challenge); + Result sendStatus(const ResponseChallenge & responseChallenge); + Result sendObjectTemp(const ResponseChallenge & responseChallenge); + Result sendAmbientTemp(const ResponseChallenge & responseChallenge); void displayImage(const std::vector<uint8_t> & imageData); };