From 9f337e3cae654fb95a2a1563f7190283fa80bd76 Mon Sep 17 00:00:00 2001 From: Patrick Fallberg Date: Thu, 5 Feb 2015 22:49:05 +0100 Subject: [PATCH 1/7] Message header updates for signing support One bit in the protocol version field is converted to a sign indicator. '1' means the message carries a signature in the unused part of the payload data buffer. Three new internal message types have been defined: * I_REQUEST_SIGNING carries a boolean and is used to exchange signing requirement information between nodes. If the I_REQUEST_SIGNING carries 'true' the receiver is informed that the sender require properly signed messages in order to be able to accept the messages. * I_GET_NONCE is passed from sender to a receiver that require signing tellig it to submit a cryptographic 'nonce' that the sender needs in order to be able to sign the message it intend to send. The I_GET_NONCE carries no payload. * I_GET_NONCE_RESPONSE is sent by the receiver of a signed message in response to I_GET_NONCE. The first byte of the payload is a signing identifier informing the sender of the signed message about the type of signature the receiver is expecting. --- libraries/MySensors/MyMessage.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libraries/MySensors/MyMessage.h b/libraries/MySensors/MyMessage.h index a46fbee..fcc0540 100644 --- a/libraries/MySensors/MyMessage.h +++ b/libraries/MySensors/MyMessage.h @@ -47,7 +47,7 @@ typedef enum { I_BATTERY_LEVEL, I_TIME, I_VERSION, I_ID_REQUEST, I_ID_RESPONSE, I_INCLUSION_MODE, I_CONFIG, I_FIND_PARENT, I_FIND_PARENT_RESPONSE, I_LOG_MESSAGE, I_CHILDREN, I_SKETCH_NAME, I_SKETCH_VERSION, - I_REBOOT, I_GATEWAY_READY + I_REBOOT, I_GATEWAY_READY, I_REQUEST_SIGNING, I_GET_NONCE, I_GET_NONCE_RESPONSE } mysensor_internal; // Type of sensor (for presentation message) @@ -84,8 +84,11 @@ typedef enum { #define BF_SET(y, x, start, len) ( y= ((y) &~ BF_MASK(start, len)) | BF_PREP(x, start, len) ) // Getters/setters for special bit fields in header -#define mSetVersion(_msg,_version) BF_SET(_msg.version_length, _version, 0, 3) -#define mGetVersion(_msg) BF_GET(_msg.version_length, 0, 3) +#define mSetVersion(_msg,_version) BF_SET(_msg.version_length, _version, 0, 2) +#define mGetVersion(_msg) BF_GET(_msg.version_length, 0, 2) + +#define mSetSigned(_msg,_signed) BF_SET(_msg.version_length, _signed, 2, 1) +#define mGetSigned(_msg) BF_GET(_msg.version_length, 2, 1) #define mSetLength(_msg,_length) BF_SET(_msg.version_length, _length, 3, 5) #define mGetLength(_msg) BF_GET(_msg.version_length, 3, 5) @@ -179,7 +182,8 @@ struct uint8_t sender; // 8 bit - Id of sender node (origin) uint8_t destination; // 8 bit - Id of destination node - uint8_t version_length; // 3 bit - Protocol version + uint8_t version_length; // 2 bit - Protocol version + // 1 bit - Signed flag // 5 bit - Length of payload uint8_t command_ack_payload; // 3 bit - Command type // 1 bit - Request an ack - Indicator that receiver should send an ack back. From c2a75d064e05e841ca386d1318a275cd658af882 Mon Sep 17 00:00:00 2001 From: Patrick Fallberg Date: Sat, 7 Feb 2015 00:19:13 +0100 Subject: [PATCH 2/7] Signing driver framework The signing driver framework resembles the radio driver framework in the sense that it relies on MyConfig.h for driver specific settings. This allows the constructor to be clean and the API less likely to need change. The API is designed with function pairs for sender and receiver of a signed message. The sender of a signed message uses putNonce() and signMsg() and the receiver of a signed message uses getNonce() and verifyMsg(). In addition, receiver is expected to call checkTimer() at regular intervals to make sure the message session does not time out. The program flow is that the sender transmit, after determining that the message needs signing, a request to the receiver for a nonce (I_GET_NONCE). The receiver calls getNonce() to obtain a nonce and transmits the nonce back to the sender (I_GET_NONCE_RESPONSE). The sender stores the received nonce in the driver with putNonce() and then calls signMsg() to sign the message to be transmitted. The receiver then calls verifyMsg() to authenticate the signed message using the nonce transmitted earlier. The flow is described below: ---------------- -------- ---------- ------------------ |senders driver| |sender| |receiver| |receivers driver| ---------------- -------- ---------- ------------------ | | I_GET_NONCE | getNonce() | | |------------------->|------------->| | | | | | setNonce() |I_GET_NONCE_RESPONSE| | |<-----------|<-------------------|<-------------| | signMsg() | | | |<-----------| | verifyMsg() | |----------->|------------------->|------------->| | | | OK / not OK | | | |<-------------| It is expected that all signing schemes is built upon this principle. --- libraries/MySensors/MySigningDriver.cpp | 4 ++ libraries/MySensors/MySigningDriver.h | 60 +++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 libraries/MySensors/MySigningDriver.cpp create mode 100644 libraries/MySensors/MySigningDriver.h diff --git a/libraries/MySensors/MySigningDriver.cpp b/libraries/MySensors/MySigningDriver.cpp new file mode 100644 index 0000000..b109a3f --- /dev/null +++ b/libraries/MySensors/MySigningDriver.cpp @@ -0,0 +1,4 @@ +#include "MySigningDriver.h" + +MySigningDriver::MySigningDriver() { +} diff --git a/libraries/MySensors/MySigningDriver.h b/libraries/MySensors/MySigningDriver.h new file mode 100644 index 0000000..28a75ea --- /dev/null +++ b/libraries/MySensors/MySigningDriver.h @@ -0,0 +1,60 @@ +/* + The MySigning driver provides a generic API for various signing backends to offer + signing of MySensors messages. + + Created by Patrick "Anticimex" Fallberg +*/ +#ifndef MySigningDriver_h +#define MySigningDriver_h + +#include +#include "MyMessage.h" + +class MySigningDriver +{ +public: + // MySigningDriver constructor. + // Different parameters would be needed depending on signing backend (e.g. pins connected etc.). + // Keeping these parameters as #define's in MyConfig to streamline the driver interface. + MySigningDriver(); + + // Stores signing identifier and a new nonce in provided message for signing operations. + // All space in message payload buffer is used for signing identifier and nonce. + // Returns false if subsystem is busy processing an ongoing signing operation. + // If successful, this marks the start of a signing operation at the receiving side so + // implementation is expected to do any necessary initializations within this call. + virtual bool getNonce(MyMessage &msg) = 0; + + // Check timeout of verification session. + // Nonce will be purged if it takes too long for a signed message to be sent to the receiver. + // This function should be called on regular intervals. + virtual bool checkTimer(void) = 0; + + // Get nonce from provided message and store for signing operations. + // Returns false if subsystem is busy processing an ongoing signing operation. + // Returns false if signing identifier found in message is not supported by the used backend. + // If successful, this marks the start of a signing operation at the sending side so + // implementation is expected to do any necessary initializations within this call. + virtual bool putNonce(MyMessage &msg) = 0; + + // Signs provided message. All remaining space in message payload buffer is used for signing + // identifier and signature. + // Nonce used for signature calculation is the one generated previously using getNonce(). + // Nonce will be cleared when this function is called to prevent re-use of nonce. + // Returns false if subsystem is busy processing an ongoing signing operation. + // Returns false if not two bytes or more of free payload space is left in provided message. + // This ends a signing operation at the sending side so implementation is expected to do any + // deinitializations and enter a power saving state within this call. + virtual bool signMsg(MyMessage &msg) = 0; + + // Verifies signature in provided message. + // Nonce used for verification is the one previously set using putNonce(). + // Nonce will be cleared when this function is called to prevent re-use of nonce. + // Returns false if subsystem is busy processing an ongoing signing operation. + // Returns false if signing identifier found in message is not supported by the used backend. + // This ends a signing operation at the receiving side so implementation is expected to do any + // deinitializations and enter a power saving state within this call. + virtual bool verifyMsg(MyMessage &msg) = 0; +}; + +#endif From fca0d4b5449c6ae6ab73f1e555be7deab99a650e Mon Sep 17 00:00:00 2001 From: Patrick Fallberg Date: Sat, 7 Feb 2015 09:52:37 +0100 Subject: [PATCH 3/7] Dummy signing driver backend The dummy signing driver implements the most rudimentary form of signing. No pre-shared key is used, nonce is simply incremeted each turn and signature is only calculated on the fist byte using data XOR nonce. This driver is intended for library debugging and sensor network integrity verification (signed messages will force the radio to transmit all 32 bytes of data). It does check SIGNING_IDENTIFIER byte to avoid illegal mixing of signing backends in the network (as seen by this node) and it does verify proper execution order on the API. The dummy driver rejects all other signing backend protocols. Signing backend is selected in MyConfig.h --- libraries/MySensors/MyConfig.h | 14 +- libraries/MySensors/MySigningDriverDummy.cpp | 143 +++++++++++++++++++ libraries/MySensors/MySigningDriverDummy.h | 36 +++++ 3 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 libraries/MySensors/MySigningDriverDummy.cpp create mode 100644 libraries/MySensors/MySigningDriverDummy.h diff --git a/libraries/MySensors/MyConfig.h b/libraries/MySensors/MyConfig.h index 96f2253..ce4f0aa 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -15,9 +15,13 @@ #define MYSENSORS_RF_NRF24 //#define MYSENSORS_RF_RF69 +// Choose signing backend by enabling one of the following +#define MYSENSORS_SIGNING_DUMMY - - +// Define a suitable timeout for a signature verification session +// Consider the turnaround from a nonce being generated to a signed message being received +// which might vary, especially in networks with many hops. 5s ought to be enough for anyone. +#define VERIFICATION_TIMEOUT_MS 5000 #ifdef MYSENSORS_RF_NRF24 #include "MyRFDriverNRF24.h" @@ -29,6 +33,12 @@ typedef class MyRFDriverNRF24 MyRFDriverClass; typedef class MyRFDriverRF69 MyRFDriverClass; #endif +#ifdef MYSENSORS_SIGNING_DUMMY +#include "MySigningDriverDummy.h" +#define SIGNING_IDENTIFIER (0) // Reserved for dummy implementation, will not work with other backends +typedef class MySigningDriverDummy MySigningDriverClass; +#endif + /*** * Enable/Disable debug logging */ diff --git a/libraries/MySensors/MySigningDriverDummy.cpp b/libraries/MySensors/MySigningDriverDummy.cpp new file mode 100644 index 0000000..51d418f --- /dev/null +++ b/libraries/MySensors/MySigningDriverDummy.cpp @@ -0,0 +1,143 @@ +/* + Dummy signing backend. Does not provide any security measures. + + Created by Patrick "Anticimex" Fallberg +*/ +#include "MySigningDriver.h" +#include "MySigningDriverDummy.h" + +#ifdef MYSENSORS_SIGNING_DUMMY +// Uncomment this to get some useful serial debug info (Serial.print and Serial.println expected) +//#define DEBUG_DUMMY_SIGNING + +#ifdef DEBUG_DUMMY_SIGNING +#define DEBUG_DUMMY_PRINTLN(args) Serial.println(args) +#else +#define DEBUG_DUMMY_PRINTLN(args) +#endif + +#ifdef DEBUG_DUMMY_SIGNING +static void DEBUG_DUMMY_PRINTBUF(char* str, uint8_t* buf, uint8_t sz) +{ + int i; + Serial.println(str); + for (int i=0; i timestamp + VERIFICATION_TIMEOUT_MS) { + DEBUG_DUMMY_PRINTLN("Verification timeout"); + // Purge nonce + current_nonce = 0; // purge nonce when used + verification_ongoing = false; + return false; + } + } + return true; +} + +bool MySigningDriverDummy::putNonce(MyMessage &msg) { + if (mGetLength(msg) != 2) { + DEBUG_DUMMY_PRINTLN("Incoming nonce with incorrect size"); + return false; // We require a signing identifier and a 1byte nonce + } + + if (((uint8_t*)msg.getCustom())[0] != SIGNING_IDENTIFIER) { + DEBUG_DUMMY_PRINTLN("Incorrect signing identifier"); + return false; + } + + current_nonce = ((uint8_t*)msg.getCustom())[1]; + return true; +} + +bool MySigningDriverDummy::signMsg(MyMessage &msg) { + // If we cannot fit any signature in the message, refuse to sign it + if (mGetLength(msg) > MAX_PAYLOAD-2) { + DEBUG_DUMMY_PRINTLN("Cannot fit any signature to this message"); + return false; + } + + // Calculate signature of message + mSetSigned(msg, 1); // make sure signing flag is set before signature is calculated + DEBUG_DUMMY_PRINTBUF("Message to sign:", (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg))); + uint8_t signature = msg.data[0] ^ current_nonce; + msg.data[mGetLength(msg)] = SIGNING_IDENTIFIER; // Add signing identifier + msg.data[mGetLength(msg)+1] = signature; + current_nonce = 0; // purge nonce when used + return true; +} + +bool MySigningDriverDummy::verifyMsg(MyMessage &msg) { + if (!verification_ongoing) { + DEBUG_DUMMY_PRINTLN("No active verification session"); + return false; + } else { + // Make sure we have not expired + if (!checkTimer()) { + return false; + } + + verification_ongoing = false; + + if (msg.data[mGetLength(msg)] != SIGNING_IDENTIFIER) { + DEBUG_DUMMY_PRINTLN("Incorrect signing identifier"); + return false; + } + + DEBUG_DUMMY_PRINTBUF("Message to verify:", (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg))); + DEBUG_DUMMY_PRINTBUF("Signature in message:", (uint8_t*)&msg.data[mGetLength(msg)], 2); + + // The dummy signing is the nonce XOR the first payload byte, + // so we "verify" by redoing that calculation with the nonce we transmitted + // Message without a payload this implementation always accepts as signed and messages that cannot + // fit a signature are also accepted as signed + if (mGetLength(msg) && mGetLength(msg) < MAX_PAYLOAD-2) { + uint8_t signature = msg.data[0] ^ current_nonce; + if (msg.data[mGetLength(msg)+1] != signature) { + DEBUG_DUMMY_PRINTBUF("Signature bad. Calculated signature:", &signature, 1); + return false; + } else { + DEBUG_DUMMY_PRINTLN("Signature ok"); + return true; + } + } else { + DEBUG_DUMMY_PRINTLN("No signature in message"); + return false; + } + } +} +#endif diff --git a/libraries/MySensors/MySigningDriverDummy.h b/libraries/MySensors/MySigningDriverDummy.h new file mode 100644 index 0000000..74b88a6 --- /dev/null +++ b/libraries/MySensors/MySigningDriverDummy.h @@ -0,0 +1,36 @@ +/* + Dummy signing backend. Does not provide any security measures. + + Created by Patrick "Anticimex" Fallberg +*/ +#ifndef MySigningDriverDummy_h +#define MySigningDriverDummy_h + +#include "MyConfig.h" +#include "MySigningDriver.h" +#include + +// The dummy signing driver implements the most rudimentary form of signing +// No pre-shared key is used, nonce is simply incremeted each turn and signature +// is only calculated on the fist byte using data XOR nonce. +// This driver is intended for library debugging and sensor network integrity +// verification (signed messages will force the radio to transmit all 32 bytes of data). +// It does check SIGNING_IDENTIFIER byte to avoid illegal mixing of signing back-ends in +// the network (as seen by this node) and it does verify proper execution order on the API. +// The dummy driver rejects all other back-ends. +class MySigningDriverDummy : public MySigningDriver +{ +public: + MySigningDriverDummy(); + bool getNonce(MyMessage &msg); + bool checkTimer(void); + bool putNonce(MyMessage &msg); + bool signMsg(MyMessage &msg); + bool verifyMsg(MyMessage &msg); +private: + unsigned long timestamp; + bool verification_ongoing; + uint8_t current_nonce; +}; + +#endif From ce290e280ea03e77bcb1288e6861f5e1557a8085 Mon Sep 17 00:00:00 2001 From: Patrick Fallberg Date: Mon, 9 Feb 2015 20:11:11 +0100 Subject: [PATCH 4/7] ATSHA204 signing backend Default SDA pin is A3 for single wire communication. The pin is selected in MyConfig.h. I2C variant of ATSHA204 is not supported. Messages are signed using HMAC-SHA256 where some parts of the message digest are ATSHA204 specific. --- libraries/MySensors/MyConfig.h | 19 + .../MySensors/MySigningDriverAtsha204.cpp | 217 ++++++++ libraries/MySensors/MySigningDriverAtsha204.h | 37 ++ libraries/MySensors/utility/ATSHA204.cpp | 521 ++++++++++++++++++ libraries/MySensors/utility/ATSHA204.h | 206 +++++++ 5 files changed, 1000 insertions(+) create mode 100644 libraries/MySensors/MySigningDriverAtsha204.cpp create mode 100644 libraries/MySensors/MySigningDriverAtsha204.h create mode 100644 libraries/MySensors/utility/ATSHA204.cpp create mode 100644 libraries/MySensors/utility/ATSHA204.h diff --git a/libraries/MySensors/MyConfig.h b/libraries/MySensors/MyConfig.h index ce4f0aa..82c02da 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -17,6 +17,7 @@ // Choose signing backend by enabling one of the following #define MYSENSORS_SIGNING_DUMMY +//#define MYSENSORS_SIGNING_ATSHA204 // Define a suitable timeout for a signature verification session // Consider the turnaround from a nonce being generated to a signed message being received @@ -39,6 +40,24 @@ typedef class MyRFDriverRF69 MyRFDriverClass; typedef class MySigningDriverDummy MySigningDriverClass; #endif +#ifdef MYSENSORS_SIGNING_ATSHA204 +#ifdef MYSENSORS_SENSOR +#define ATSHA204_PIN 17 //A3 +#endif + +#ifdef MYSENSORS_SERIAL_GATEWAY +#define ATSHA204_PIN 17 //A3 +#endif + +#ifdef MYSENSORS_ETHERNET_MQTT_GATEWAY +#define ATSHA204_PIN 17 //A3 +#endif + +#include "MySigningDriverAtsha204.h" +#define SIGNING_IDENTIFIER (1) // SHA256-based HMAC (ATSHA204 specific) +typedef class MySigningDriverAtsha204 MySigningDriverClass; +#endif + /*** * Enable/Disable debug logging */ diff --git a/libraries/MySensors/MySigningDriverAtsha204.cpp b/libraries/MySensors/MySigningDriverAtsha204.cpp new file mode 100644 index 0000000..a71a30a --- /dev/null +++ b/libraries/MySensors/MySigningDriverAtsha204.cpp @@ -0,0 +1,217 @@ +/* + ATSHA204 signing backend. The Atmel ATSHA204 offers true random number generation and + HMAC-SHA256 authentication with a readout-protected key. + + Created by Patrick "Anticimex" Fallberg +*/ +#include "MySigningDriver.h" +#include "MySigningDriverAtsha204.h" + +#ifdef MYSENSORS_SIGNING_ATSHA204 +// Uncomment this to get some useful serial debug info (Serial.print and Serial.println expected) +//#define DEBUG_ATSHA_SIGNING + +#ifdef DEBUG_ATSHA_SIGNING +#define DEBUG_ATSHA_PRINTLN(args) Serial.println(args) +#else +#define DEBUG_ATSHA_PRINTLN(args) +#endif + +#ifdef DEBUG_ATSHA_SIGNING +static void DEBUG_ATSHA_PRINTBUF(char* str, uint8_t* buf, uint8_t sz) +{ + int i; + Serial.println(str); + for (int i=0; i timestamp + VERIFICATION_TIMEOUT_MS) { + DEBUG_ATSHA_PRINTLN("Verification timeout"); + // Purge nonce + memset(current_nonce, 0xAA, NONCE_NUMIN_SIZE_PASSTHROUGH); + verification_ongoing = false; + return false; + } + } + return true; +} + +bool MySigningDriverAtsha204::putNonce(MyMessage &msg) { + if (mGetLength(msg) != MAX_PAYLOAD) { + DEBUG_ATSHA_PRINTLN("Incoming nonce with incorrect size"); + return false; // We require as big nonce as possible + } + + if (((uint8_t*)msg.getCustom())[0] != SIGNING_IDENTIFIER) { + DEBUG_ATSHA_PRINTLN("Incorrect signing identifier"); + return false; + } + + memcpy(current_nonce, (uint8_t*)msg.getCustom(), MAX_PAYLOAD); + return true; +} + +bool MySigningDriverAtsha204::signMsg(MyMessage &msg) { + // If we cannot fit any signature in the message, refuse to sign it + if (mGetLength(msg) > MAX_PAYLOAD-2) { + DEBUG_ATSHA_PRINTLN("Cannot fit any signature to this message"); + return false; + } + + // Calculate signature of message + mSetSigned(msg, 1); // make sure signing flag is set before signature is calculated + DEBUG_ATSHA_PRINTBUF("Message to sign:", (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg))); + if (!calculateSignature(msg)) { + DEBUG_ATSHA_PRINTLN("Failed to calculate signature"); + mSetSigned(msg, 0); // make sure signing flag is truthful + return false; + } + + // Transfer as much signature data as the remaining space in the message permits + memcpy(&msg.data[mGetLength(msg)], &rx_buffer[SHA204_BUFFER_POS_DATA], MAX_PAYLOAD-mGetLength(msg)); + + return true; +} + +bool MySigningDriverAtsha204::verifyMsg(MyMessage &msg) { + if (!verification_ongoing) { + DEBUG_ATSHA_PRINTLN("No active verification session"); + return false; + } else { + // Make sure we have not expired + if (!checkTimer()) { + return false; + } + + verification_ongoing = false; + + if (msg.data[mGetLength(msg)] != SIGNING_IDENTIFIER) { + DEBUG_ATSHA_PRINTLN("Incorrect signing identifier"); + return false; + } + + DEBUG_ATSHA_PRINTBUF("Message to verify:", (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg))); + DEBUG_ATSHA_PRINTBUF("Signature in message:", (uint8_t*)&msg.data[mGetLength(msg)], MAX_PAYLOAD-mGetLength(msg)); + + // Get signature of message + if (!calculateSignature(msg)) { + DEBUG_ATSHA_PRINTLN("Failed to calculate signature"); + return false; + } + + // Compare the caluclated signature with the provided signature + if (memcmp(&msg.data[mGetLength(msg)], &rx_buffer[SHA204_BUFFER_POS_DATA], MAX_PAYLOAD-mGetLength(msg))) { + DEBUG_ATSHA_PRINTBUF("Signature bad. Calculated signature:", &rx_buffer[SHA204_BUFFER_POS_DATA], MAX_PAYLOAD-mGetLength(msg)); + return false; + } else { + DEBUG_ATSHA_PRINTLN("Signature ok"); + return true; + } + } +} + +// Helper to calculate signature of msg (returned in rx_buffer[SHA204_BUFFER_POS_DATA]) +bool MySigningDriverAtsha204::calculateSignature(MyMessage &msg) { + memset(temp_message, 0, 32); + memcpy(temp_message, (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg))); + + // Wake device before starting operations + if (atsha204.sha204c_wakeup(rx_buffer) != SHA204_SUCCESS) return false; + + // Program the data to sign into the ATSHA204 + if (atsha204.sha204m_execute(SHA204_WRITE, SHA204_ZONE_DATA | SHA204_ZONE_COUNT_FLAG, 8 << 3, 32, temp_message, + WRITE_COUNT_LONG, tx_buffer, WRITE_RSP_SIZE, rx_buffer) != SHA204_SUCCESS) { + DEBUG_ATSHA_PRINTLN("Failed to program data"); + return false; + } + + // Program the nonce to use for the signature (has to be done just before GENDIG due to chip limitations) + if (atsha204.sha204m_execute(SHA204_NONCE, NONCE_MODE_PASSTHROUGH, 0, NONCE_NUMIN_SIZE_PASSTHROUGH, current_nonce, + NONCE_COUNT_LONG, tx_buffer, NONCE_RSP_SIZE_SHORT, rx_buffer) != SHA204_SUCCESS) { + DEBUG_ATSHA_PRINTLN("Failed to program nonce"); + // Purge nonce when used + memset(current_nonce, 0xAA, NONCE_NUMIN_SIZE_PASSTHROUGH); + return false; + } + + // Purge nonce when used + memset(current_nonce, 0xAA, NONCE_NUMIN_SIZE_PASSTHROUGH); + + // Generate digest of data and nonce + if (atsha204.sha204m_execute(SHA204_GENDIG, GENDIG_ZONE_DATA, 8, 0, NULL, + GENDIG_COUNT_DATA, tx_buffer, GENDIG_RSP_SIZE, rx_buffer) != SHA204_SUCCESS) { + DEBUG_ATSHA_PRINTLN("Failed to generate digest"); + return false; + } + + // Calculate HMAC of message+nonce digest and secret key + if (atsha204.sha204m_execute(SHA204_HMAC, HMAC_MODE_SOURCE_FLAG_MATCH, 0, 0, NULL, + HMAC_COUNT, tx_buffer, HMAC_RSP_SIZE, rx_buffer) != SHA204_SUCCESS) { + DEBUG_ATSHA_PRINTLN("Failed to generate HMAC"); + return false; + } + + // Put device back to sleep + atsha204.sha204c_sleep(); + + // Overwrite the first byte in the signature with the signing identifier + rx_buffer[SHA204_BUFFER_POS_DATA] = SIGNING_IDENTIFIER; + + DEBUG_ATSHA_PRINTBUF("HMAC:", &rx_buffer[SHA204_BUFFER_POS_DATA], 32); + return true; // We return with the signature in rx_buffer[SHA204_BUFFER_POS_DATA] +} +#endif diff --git a/libraries/MySensors/MySigningDriverAtsha204.h b/libraries/MySensors/MySigningDriverAtsha204.h new file mode 100644 index 0000000..a2f6b9d --- /dev/null +++ b/libraries/MySensors/MySigningDriverAtsha204.h @@ -0,0 +1,37 @@ +/* + ATSHA204 signing backend. The Atmel ATSHA204 offers true random number generation and + HMAC-SHA256 authentication with a readout-protected key. + + Created by Patrick "Anticimex" Fallberg +*/ +#ifndef MySigningDriverAtsha204_h +#define MySigningDriverAtsha204_h + +#include "MyConfig.h" +#include "MySigningDriver.h" +#include "utility/ATSHA204.h" +#include + +// The ATSHA204 is capable of generating proper random numbers for nonce +// and can calculate HMAC-SHA256 signatures. This is enterprise- +// level of security and ought to implement the signing needs for anybody. +class MySigningDriverAtsha204 : public MySigningDriver +{ +public: + MySigningDriverAtsha204(); + bool getNonce(MyMessage &msg); + bool checkTimer(void); + bool putNonce(MyMessage &msg); + bool signMsg(MyMessage &msg); + bool verifyMsg(MyMessage &msg); +private: + unsigned long timestamp; + bool verification_ongoing; + uint8_t current_nonce[NONCE_NUMIN_SIZE_PASSTHROUGH]; + uint8_t temp_message[32]; + uint8_t rx_buffer[SHA204_RSP_SIZE_MAX]; + uint8_t tx_buffer[SHA204_CMD_SIZE_MAX]; + bool calculateSignature(MyMessage &msg); +}; + +#endif diff --git a/libraries/MySensors/utility/ATSHA204.cpp b/libraries/MySensors/utility/ATSHA204.cpp new file mode 100644 index 0000000..da63668 --- /dev/null +++ b/libraries/MySensors/utility/ATSHA204.cpp @@ -0,0 +1,521 @@ +#include "Arduino.h" +#include "ATSHA204.h" + +// atsha204Class Constructor +// Feed this function the Arduino-ized pin number you want to assign to the ATSHA204's SDA pin +// This will find the DDRX, PORTX, and PINX registrs it'll need to point to to control that pin +// As well as the bit value for each of those registers +atsha204Class::atsha204Class(uint8_t pin) +{ + device_pin = digitalPinToBitMask(pin); // Find the bit value of the pin + uint8_t port = digitalPinToPort(pin); // temoporarily used to get the next three registers + + // Point to data direction register port of pin + device_port_DDR = portModeRegister(port); + // Point to output register of pin + device_port_OUT = portOutputRegister(port); + // Point to input register of pin + device_port_IN = portInputRegister(port); +} + +/* SWI bit bang functions */ + +void atsha204Class::swi_set_signal_pin(uint8_t is_high) +{ + *device_port_DDR |= device_pin; + + if (is_high) + *device_port_OUT |= device_pin; + else + *device_port_OUT &= ~device_pin; +} + +uint8_t atsha204Class::swi_send_bytes(uint8_t count, uint8_t *buffer) +{ + uint8_t i, bit_mask; + + // Disable interrupts while sending. + noInterrupts(); //swi_disable_interrupts(); + + // Set signal pin as output. + *device_port_OUT |= device_pin; + *device_port_DDR |= device_pin; + + // Wait turn around time. + delayMicroseconds(RX_TX_DELAY); //RX_TX_DELAY; + + for (i = 0; i < count; i++) + { + for (bit_mask = 1; bit_mask > 0; bit_mask <<= 1) + { + if (bit_mask & buffer[i]) + { + *device_port_OUT &= ~device_pin; + delayMicroseconds(BIT_DELAY); //BIT_DELAY_1; + *device_port_OUT |= device_pin; + delayMicroseconds(7*BIT_DELAY); //BIT_DELAY_7; + } + else + { + // Send a zero bit. + *device_port_OUT &= ~device_pin; + delayMicroseconds(BIT_DELAY); //BIT_DELAY_1; + *device_port_OUT |= device_pin; + delayMicroseconds(BIT_DELAY); //BIT_DELAY_1; + *device_port_OUT &= ~device_pin; + delayMicroseconds(BIT_DELAY); //BIT_DELAY_1; + *device_port_OUT |= device_pin; + delayMicroseconds(5*BIT_DELAY); //BIT_DELAY_5; + } + } + } + interrupts(); //swi_enable_interrupts(); + return SWI_FUNCTION_RETCODE_SUCCESS; +} + +uint8_t atsha204Class::swi_send_byte(uint8_t value) +{ + return swi_send_bytes(1, &value); +} + +uint8_t atsha204Class::swi_receive_bytes(uint8_t count, uint8_t *buffer) +{ + uint8_t status = SWI_FUNCTION_RETCODE_SUCCESS; + uint8_t i; + uint8_t bit_mask; + uint8_t pulse_count; + uint8_t timeout_count; + + // Disable interrupts while receiving. + noInterrupts(); //swi_disable_interrupts(); + + // Configure signal pin as input. + *device_port_DDR &= ~device_pin; + + // Receive bits and store in buffer. + for (i = 0; i < count; i++) + { + for (bit_mask = 1; bit_mask > 0; bit_mask <<= 1) + { + pulse_count = 0; + + // Make sure that the variable below is big enough. + // Change it to uint16_t if 255 is too small, but be aware that + // the loop resolution decreases on an 8-bit controller in that case. + timeout_count = START_PULSE_TIME_OUT; + + // Detect start bit. + while (--timeout_count > 0) + { + // Wait for falling edge. + if ((*device_port_IN & device_pin) == 0) + break; + } + + if (timeout_count == 0) + { + status = SWI_FUNCTION_RETCODE_TIMEOUT; + break; + } + + do + { + // Wait for rising edge. + if ((*device_port_IN & device_pin) != 0) + { + // For an Atmel microcontroller this might be faster than "pulse_count++". + pulse_count = 1; + break; + } + } while (--timeout_count > 0); + + if (pulse_count == 0) + { + status = SWI_FUNCTION_RETCODE_TIMEOUT; + break; + } + + // Trying to measure the time of start bit and calculating the timeout + // for zero bit detection is not accurate enough for an 8 MHz 8-bit CPU. + // So let's just wait the maximum time for the falling edge of a zero bit + // to arrive after we have detected the rising edge of the start bit. + timeout_count = ZERO_PULSE_TIME_OUT; + + // Detect possible edge indicating zero bit. + do + { + if ((*device_port_IN & device_pin) == 0) + { + // For an Atmel microcontroller this might be faster than "pulse_count++". + pulse_count = 2; + break; + } + } while (--timeout_count > 0); + + // Wait for rising edge of zero pulse before returning. Otherwise we might interpret + // its rising edge as the next start pulse. + if (pulse_count == 2) + { + do + { + if ((*device_port_IN & device_pin) != 0) + break; + } while (timeout_count-- > 0); + } + + // Update byte at current buffer index. + else + buffer[i] |= bit_mask; // received "one" bit + } + + if (status != SWI_FUNCTION_RETCODE_SUCCESS) + break; + } + interrupts(); //swi_enable_interrupts(); + + if (status == SWI_FUNCTION_RETCODE_TIMEOUT) + { + if (i > 0) + // Indicate that we timed out after having received at least one byte. + status = SWI_FUNCTION_RETCODE_RX_FAIL; + } + return status; +} + +/* Physical functions */ + +uint8_t atsha204Class::sha204c_sleep() +{ + return swi_send_byte(SHA204_SWI_FLAG_SLEEP); +} + +uint8_t atsha204Class::sha204p_receive_response(uint8_t size, uint8_t *response) +{ + uint8_t count_byte; + uint8_t i; + uint8_t ret_code; + + for (i = 0; i < size; i++) + response[i] = 0; + + (void) swi_send_byte(SHA204_SWI_FLAG_TX); + + ret_code = swi_receive_bytes(size, response); + if (ret_code == SWI_FUNCTION_RETCODE_SUCCESS || ret_code == SWI_FUNCTION_RETCODE_RX_FAIL) + { + count_byte = response[SHA204_BUFFER_POS_COUNT]; + if ((count_byte < SHA204_RSP_SIZE_MIN) || (count_byte > size)) + return SHA204_INVALID_SIZE; + + return SHA204_SUCCESS; + } + + // Translate error so that the Communication layer + // can distinguish between a real error or the + // device being busy executing a command. + if (ret_code == SWI_FUNCTION_RETCODE_TIMEOUT) + return SHA204_RX_NO_RESPONSE; + else + return SHA204_RX_FAIL; +} + +/* Communication functions */ + +uint8_t atsha204Class::sha204c_wakeup(uint8_t *response) +{ + swi_set_signal_pin(0); + delayMicroseconds(10*SHA204_WAKEUP_PULSE_WIDTH); + swi_set_signal_pin(1); + delay(SHA204_WAKEUP_DELAY); + + uint8_t ret_code = sha204p_receive_response(SHA204_RSP_SIZE_MIN, response); + if (ret_code != SHA204_SUCCESS) + return ret_code; + + // Verify status response. + if (response[SHA204_BUFFER_POS_COUNT] != SHA204_RSP_SIZE_MIN) + ret_code = SHA204_INVALID_SIZE; + else if (response[SHA204_BUFFER_POS_STATUS] != SHA204_STATUS_BYTE_WAKEUP) + ret_code = SHA204_COMM_FAIL; + else + { + if ((response[SHA204_RSP_SIZE_MIN - SHA204_CRC_SIZE] != 0x33) + || (response[SHA204_RSP_SIZE_MIN + 1 - SHA204_CRC_SIZE] != 0x43)) + ret_code = SHA204_BAD_CRC; + } + if (ret_code != SHA204_SUCCESS) + delay(SHA204_COMMAND_EXEC_MAX); + + return ret_code; +} + +uint8_t atsha204Class::sha204c_resync(uint8_t size, uint8_t *response) +{ + // Try to re-synchronize without sending a Wake token + // (step 1 of the re-synchronization process). + delay(SHA204_SYNC_TIMEOUT); + uint8_t ret_code = sha204p_receive_response(size, response); + if (ret_code == SHA204_SUCCESS) + return ret_code; + + // We lost communication. Send a Wake pulse and try + // to receive a response (steps 2 and 3 of the + // re-synchronization process). + (void) sha204c_sleep(); + ret_code = sha204c_wakeup(response); + + // Translate a return value of success into one + // that indicates that the device had to be woken up + // and might have lost its TempKey. + return (ret_code == SHA204_SUCCESS ? SHA204_RESYNC_WITH_WAKEUP : ret_code); +} + +uint8_t atsha204Class::sha204c_send_and_receive(uint8_t *tx_buffer, uint8_t rx_size, uint8_t *rx_buffer, uint8_t execution_delay, uint8_t execution_timeout) +{ + uint8_t ret_code = SHA204_FUNC_FAIL; + uint8_t ret_code_resync; + uint8_t n_retries_send; + uint8_t n_retries_receive; + uint8_t i; + uint8_t status_byte; + uint8_t count = tx_buffer[SHA204_BUFFER_POS_COUNT]; + uint8_t count_minus_crc = count - SHA204_CRC_SIZE; + uint16_t execution_timeout_us = (uint16_t) (execution_timeout * 1000) + SHA204_RESPONSE_TIMEOUT; + volatile uint16_t timeout_countdown; + + // Append CRC. + sha204c_calculate_crc(count_minus_crc, tx_buffer, tx_buffer + count_minus_crc); + + // Retry loop for sending a command and receiving a response. + n_retries_send = SHA204_RETRY_COUNT + 1; + + while ((n_retries_send-- > 0) && (ret_code != SHA204_SUCCESS)) + { + // Send command. + ret_code = swi_send_byte(SHA204_SWI_FLAG_CMD); + if (ret_code != SWI_FUNCTION_RETCODE_SUCCESS) + ret_code = SHA204_COMM_FAIL; + else + ret_code = swi_send_bytes(count, tx_buffer); + + if (ret_code != SHA204_SUCCESS) + { + if (sha204c_resync(rx_size, rx_buffer) == SHA204_RX_NO_RESPONSE) + return ret_code; // The device seems to be dead in the water. + else + continue; + } + + // Wait minimum command execution time and then start polling for a response. + delay(execution_delay); + + // Retry loop for receiving a response. + n_retries_receive = SHA204_RETRY_COUNT + 1; + while (n_retries_receive-- > 0) + { + // Reset response buffer. + for (i = 0; i < rx_size; i++) + rx_buffer[i] = 0; + + // Poll for response. + timeout_countdown = execution_timeout_us; + do + { + ret_code = sha204p_receive_response(rx_size, rx_buffer); + timeout_countdown -= SHA204_RESPONSE_TIMEOUT; + } + while ((timeout_countdown > SHA204_RESPONSE_TIMEOUT) && (ret_code == SHA204_RX_NO_RESPONSE)); + + if (ret_code == SHA204_RX_NO_RESPONSE) + { + // We did not receive a response. Re-synchronize and send command again. + if (sha204c_resync(rx_size, rx_buffer) == SHA204_RX_NO_RESPONSE) + // The device seems to be dead in the water. + return ret_code; + else + break; + } + + // Check whether we received a valid response. + if (ret_code == SHA204_INVALID_SIZE) + { + // We see 0xFF for the count when communication got out of sync. + ret_code_resync = sha204c_resync(rx_size, rx_buffer); + if (ret_code_resync == SHA204_SUCCESS) + // We did not have to wake up the device. Try receiving response again. + continue; + if (ret_code_resync == SHA204_RESYNC_WITH_WAKEUP) + // We could re-synchronize, but only after waking up the device. + // Re-send command. + break; + else + // We failed to re-synchronize. + return ret_code; + } + + // We received a response of valid size. + // Check the consistency of the response. + ret_code = sha204c_check_crc(rx_buffer); + if (ret_code == SHA204_SUCCESS) + { + // Received valid response. + if (rx_buffer[SHA204_BUFFER_POS_COUNT] > SHA204_RSP_SIZE_MIN) + // Received non-status response. We are done. + return ret_code; + + // Received status response. + status_byte = rx_buffer[SHA204_BUFFER_POS_STATUS]; + + // Translate the three possible device status error codes + // into library return codes. + if (status_byte == SHA204_STATUS_BYTE_PARSE) + return SHA204_PARSE_ERROR; + if (status_byte == SHA204_STATUS_BYTE_EXEC) + return SHA204_CMD_FAIL; + if (status_byte == SHA204_STATUS_BYTE_COMM) + { + // In case of the device status byte indicating a communication + // error this function exits the retry loop for receiving a response + // and enters the overall retry loop + // (send command / receive response). + ret_code = SHA204_STATUS_CRC; + break; + } + + // Received status response from CheckMAC, DeriveKey, GenDig, + // Lock, Nonce, Pause, UpdateExtra, or Write command. + return ret_code; + } + + else + { + // Received response with incorrect CRC. + ret_code_resync = sha204c_resync(rx_size, rx_buffer); + if (ret_code_resync == SHA204_SUCCESS) + // We did not have to wake up the device. Try receiving response again. + continue; + if (ret_code_resync == SHA204_RESYNC_WITH_WAKEUP) + // We could re-synchronize, but only after waking up the device. + // Re-send command. + break; + else + // We failed to re-synchronize. + return ret_code; + } // block end of check response consistency + + } // block end of receive retry loop + + } // block end of send and receive retry loop + + return ret_code; +} + + +/* Marshaling functions */ +uint8_t atsha204Class::sha204m_execute(uint8_t op_code, uint8_t param1, uint16_t param2, + uint8_t datalen1, uint8_t *data1, uint8_t tx_size, uint8_t *tx_buffer, uint8_t rx_size, uint8_t *rx_buffer) +{ + uint8_t poll_delay, poll_timeout, response_size; + uint8_t *p_buffer; + uint8_t len; + + // Supply delays and response size. + switch (op_code) + { + case SHA204_GENDIG: + poll_delay = GENDIG_DELAY; + poll_timeout = GENDIG_EXEC_MAX - GENDIG_DELAY; + response_size = GENDIG_RSP_SIZE; + break; + + case SHA204_HMAC: + poll_delay = HMAC_DELAY; + poll_timeout = HMAC_EXEC_MAX - HMAC_DELAY; + response_size = HMAC_RSP_SIZE; + break; + + case SHA204_NONCE: + poll_delay = NONCE_DELAY; + poll_timeout = NONCE_EXEC_MAX - NONCE_DELAY; + response_size = param1 == NONCE_MODE_PASSTHROUGH + ? NONCE_RSP_SIZE_SHORT : NONCE_RSP_SIZE_LONG; + break; + + case SHA204_RANDOM: + poll_delay = RANDOM_DELAY; + poll_timeout = RANDOM_EXEC_MAX - RANDOM_DELAY; + response_size = RANDOM_RSP_SIZE; + break; + + case SHA204_WRITE: + poll_delay = WRITE_DELAY; + poll_timeout = WRITE_EXEC_MAX - WRITE_DELAY; + response_size = WRITE_RSP_SIZE; + break; + + default: + poll_delay = 0; + poll_timeout = SHA204_COMMAND_EXEC_MAX; + response_size = rx_size; + } + + // Assemble command. + len = datalen1 + SHA204_CMD_SIZE_MIN; + p_buffer = tx_buffer; + *p_buffer++ = len; + *p_buffer++ = op_code; + *p_buffer++ = param1; + *p_buffer++ = param2 & 0xFF; + *p_buffer++ = param2 >> 8; + + if (datalen1 > 0) { + memcpy(p_buffer, data1, datalen1); + p_buffer += datalen1; + } + + sha204c_calculate_crc(len - SHA204_CRC_SIZE, tx_buffer, p_buffer); + + // Send command and receive response. + return sha204c_send_and_receive(&tx_buffer[0], response_size, + &rx_buffer[0], poll_delay, poll_timeout); +} + +/* CRC Calculator and Checker */ + +void atsha204Class::sha204c_calculate_crc(uint8_t length, uint8_t *data, uint8_t *crc) +{ + uint8_t counter; + uint16_t crc_register = 0; + uint16_t polynom = 0x8005; + uint8_t shift_register; + uint8_t data_bit, crc_bit; + + for (counter = 0; counter < length; counter++) + { + for (shift_register = 0x01; shift_register > 0x00; shift_register <<= 1) + { + data_bit = (data[counter] & shift_register) ? 1 : 0; + crc_bit = crc_register >> 15; + + // Shift CRC to the left by 1. + crc_register <<= 1; + + if ((data_bit ^ crc_bit) != 0) + crc_register ^= polynom; + } + } + crc[0] = (uint8_t) (crc_register & 0x00FF); + crc[1] = (uint8_t) (crc_register >> 8); +} + +uint8_t atsha204Class::sha204c_check_crc(uint8_t *response) +{ + uint8_t crc[SHA204_CRC_SIZE]; + uint8_t count = response[SHA204_BUFFER_POS_COUNT]; + + count -= SHA204_CRC_SIZE; + sha204c_calculate_crc(count, response, crc); + + return (crc[0] == response[count] && crc[1] == response[count + 1]) + ? SHA204_SUCCESS : SHA204_BAD_CRC; +} \ No newline at end of file diff --git a/libraries/MySensors/utility/ATSHA204.h b/libraries/MySensors/utility/ATSHA204.h new file mode 100644 index 0000000..cb2abc8 --- /dev/null +++ b/libraries/MySensors/utility/ATSHA204.h @@ -0,0 +1,206 @@ +#ifndef ATSHA204_H +#define ATSHA204_H +#include "Arduino.h" + +/* This is a scaled down variant of the ATSHA204 library, tweaked to meet the specific needs of MySensors. */ + +/* Library return codes */ +#define SHA204_SUCCESS ((uint8_t) 0x00) //!< Function succeeded. +#define SHA204_PARSE_ERROR ((uint8_t) 0xD2) //!< response status byte indicates parsing error +#define SHA204_CMD_FAIL ((uint8_t) 0xD3) //!< response status byte indicates command execution error +#define SHA204_STATUS_CRC ((uint8_t) 0xD4) //!< response status byte indicates CRC error +#define SHA204_STATUS_UNKNOWN ((uint8_t) 0xD5) //!< response status byte is unknown +#define SHA204_FUNC_FAIL ((uint8_t) 0xE0) //!< Function could not execute due to incorrect condition / state. +#define SHA204_GEN_FAIL ((uint8_t) 0xE1) //!< unspecified error +#define SHA204_BAD_PARAM ((uint8_t) 0xE2) //!< bad argument (out of range, null pointer, etc.) +#define SHA204_INVALID_ID ((uint8_t) 0xE3) //!< invalid device id, id not set +#define SHA204_INVALID_SIZE ((uint8_t) 0xE4) //!< Count value is out of range or greater than buffer size. +#define SHA204_BAD_CRC ((uint8_t) 0xE5) //!< incorrect CRC received +#define SHA204_RX_FAIL ((uint8_t) 0xE6) //!< Timed out while waiting for response. Number of bytes received is > 0. +#define SHA204_RX_NO_RESPONSE ((uint8_t) 0xE7) //!< Not an error while the Command layer is polling for a command response. +#define SHA204_RESYNC_WITH_WAKEUP ((uint8_t) 0xE8) //!< re-synchronization succeeded, but only after generating a Wake-up + +#define SHA204_COMM_FAIL ((uint8_t) 0xF0) //!< Communication with device failed. Same as in hardware dependent modules. +#define SHA204_TIMEOUT ((uint8_t) 0xF1) //!< Timed out while waiting for response. Number of bytes received is 0. + +/* bitbang_config.h */ + +#define PORT_ACCESS_TIME (630) //! time it takes to toggle the pin at CPU clock of 16 MHz (ns) +#define START_PULSE_WIDTH (4340) //! width of start pulse (ns) +#define BIT_DELAY (4) //! delay macro for width of one pulse (start pulse or zero pulse, in ns) +#define RX_TX_DELAY (15) //! turn around time when switching from receive to transmit +#define START_PULSE_TIME_OUT (255) //! This value is decremented while waiting for the falling edge of a start pulse. +#define ZERO_PULSE_TIME_OUT (26) //! This value is decremented while waiting for the falling edge of a zero pulse. + +/* swi_phys.h */ + +#define SWI_FUNCTION_RETCODE_SUCCESS ((uint8_t) 0x00) //!< Communication with device succeeded. +#define SWI_FUNCTION_RETCODE_TIMEOUT ((uint8_t) 0xF1) //!< Communication timed out. +#define SWI_FUNCTION_RETCODE_RX_FAIL ((uint8_t) 0xF9) //!< Communication failed after at least one byte was received. + +/* sha204_physical.h */ + +#define SHA204_RSP_SIZE_MIN ((uint8_t) 4) //!< minimum number of bytes in response +#define SHA204_RSP_SIZE_MAX ((uint8_t) 35) //!< maximum size of response packet +#define SHA204_BUFFER_POS_COUNT (0) //!< buffer index of count byte in command or response +#define SHA204_BUFFER_POS_DATA (1) //!< buffer index of data in response +#define SHA204_WAKEUP_PULSE_WIDTH (uint8_t) (6.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5) //! width of Wakeup pulse in 10 us units +#define SHA204_WAKEUP_DELAY (uint8_t) (3.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5) //! delay between Wakeup pulse and communication in ms + +/* sha204_swi.c */ +#define SHA204_SWI_FLAG_CMD ((uint8_t) 0x77) //!< flag preceding a command +#define SHA204_SWI_FLAG_TX ((uint8_t) 0x88) //!< flag requesting a response +#define SHA204_SWI_FLAG_IDLE ((uint8_t) 0xBB) //!< flag requesting to go into Idle mode +#define SHA204_SWI_FLAG_SLEEP ((uint8_t) 0xCC) //!< flag requesting to go into Sleep mode + +/* sha204_comm_marshaling.h */ +// command op-code definitions +#define SHA204_GENDIG ((uint8_t) 0x15) //!< GenDig command op-code +#define SHA204_HMAC ((uint8_t) 0x11) //!< HMAC command op-code +#define SHA204_NONCE ((uint8_t) 0x16) //!< Nonce command op-code +#define SHA204_RANDOM ((uint8_t) 0x1B) //!< Random command op-code +#define SHA204_WRITE ((uint8_t) 0x12) //!< Write command op-code + +// packet size definitions +#define SHA204_RSP_SIZE_VAL ((uint8_t) 7) //!< size of response packet containing four bytes of data + +// definitions for command packet indexes common to all commands +#define SHA204_COUNT_IDX ( 0) //!< command packet index for count +#define SHA204_OPCODE_IDX ( 1) //!< command packet index for op-code +#define SHA204_PARAM1_IDX ( 2) //!< command packet index for first parameter +#define SHA204_PARAM2_IDX ( 3) //!< command packet index for second parameter +#define SHA204_DATA_IDX ( 5) //!< command packet index for second parameter + +// zone definitions +#define SHA204_ZONE_CONFIG ((uint8_t) 0x00) //!< Configuration zone +#define SHA204_ZONE_OTP ((uint8_t) 0x01) //!< OTP (One Time Programming) zone +#define SHA204_ZONE_DATA ((uint8_t) 0x02) //!< Data zone +#define SHA204_ZONE_MASK ((uint8_t) 0x03) //!< Zone mask +#define SHA204_ZONE_COUNT_FLAG ((uint8_t) 0x80) //!< Zone bit 7 set: Access 32 bytes, otherwise 4 bytes. +#define SHA204_ZONE_ACCESS_4 ((uint8_t) 4) //!< Read or write 4 bytes. +#define SHA204_ZONE_ACCESS_32 ((uint8_t) 32) //!< Read or write 32 bytes. +#define SHA204_ADDRESS_MASK_CONFIG ( 0x001F) //!< Address bits 5 to 7 are 0 for Configuration zone. +#define SHA204_ADDRESS_MASK_OTP ( 0x000F) //!< Address bits 4 to 7 are 0 for OTP zone. +#define SHA204_ADDRESS_MASK ( 0x007F) //!< Address bit 7 to 15 are always 0. + +// GenDig command definitions +#define GENDIG_ZONE_IDX SHA204_PARAM1_IDX //!< GenDig command index for zone +#define GENDIG_KEYID_IDX SHA204_PARAM2_IDX //!< GenDig command index for key id +#define GENDIG_DATA_IDX SHA204_DATA_IDX //!< GenDig command index for optional data +#define GENDIG_COUNT SHA204_CMD_SIZE_MIN //!< GenDig command packet size without "other data" +#define GENDIG_COUNT_DATA (11) //!< GenDig command packet size with "other data" +#define GENDIG_OTHER_DATA_SIZE (4) //!< GenDig size of "other data" +#define GENDIG_ZONE_CONFIG ((uint8_t) 0) //!< GenDig zone id config +#define GENDIG_ZONE_OTP ((uint8_t) 1) //!< GenDig zone id OTP +#define GENDIG_ZONE_DATA ((uint8_t) 2) //!< GenDig zone id data + +// HMAC command definitions +#define HMAC_MODE_IDX SHA204_PARAM1_IDX //!< HMAC command index for mode +#define HMAC_KEYID_IDX SHA204_PARAM2_IDX //!< HMAC command index for key id +#define HMAC_COUNT SHA204_CMD_SIZE_MIN //!< HMAC command packet size +#define HMAC_MODE_MASK ((uint8_t) 0x74) //!< HMAC mode bits 0, 1, 3, and 7 are 0. +#define HMAC_MODE_SOURCE_FLAG_MATCH ((uint8_t) 0x04) //!< HMAC mode bit 2: match TempKey.SourceFlag + +// Nonce command definitions +#define NONCE_MODE_IDX SHA204_PARAM1_IDX //!< Nonce command index for mode +#define NONCE_PARAM2_IDX SHA204_PARAM2_IDX //!< Nonce command index for 2. parameter +#define NONCE_INPUT_IDX SHA204_DATA_IDX //!< Nonce command index for input data +#define NONCE_COUNT_SHORT (27) //!< Nonce command packet size for 20 bytes of data +#define NONCE_COUNT_LONG (39) //!< Nonce command packet size for 32 bytes of data +#define NONCE_MODE_MASK ((uint8_t) 3) //!< Nonce mode bits 2 to 7 are 0. +#define NONCE_MODE_SEED_UPDATE ((uint8_t) 0x00) //!< Nonce mode: update seed +#define NONCE_MODE_NO_SEED_UPDATE ((uint8_t) 0x01) //!< Nonce mode: do not update seed +#define NONCE_MODE_INVALID ((uint8_t) 0x02) //!< Nonce mode 2 is invalid. +#define NONCE_MODE_PASSTHROUGH ((uint8_t) 0x03) //!< Nonce mode: pass-through +#define NONCE_NUMIN_SIZE (20) //!< Nonce data length +#define NONCE_NUMIN_SIZE_PASSTHROUGH (32) //!< Nonce data length in pass-through mode (mode = 3) + +// Random command definitions +#define RANDOM_MODE_IDX SHA204_PARAM1_IDX //!< Random command index for mode +#define RANDOM_PARAM2_IDX SHA204_PARAM2_IDX //!< Random command index for 2. parameter +#define RANDOM_COUNT SHA204_CMD_SIZE_MIN //!< Random command packet size +#define RANDOM_SEED_UPDATE ((uint8_t) 0x00) //!< Random mode for automatic seed update +#define RANDOM_NO_SEED_UPDATE ((uint8_t) 0x01) //!< Random mode for no seed update + +// Write command definitions +#define WRITE_ZONE_IDX SHA204_PARAM1_IDX //!< Write command index for zone +#define WRITE_ADDR_IDX SHA204_PARAM2_IDX //!< Write command index for address +#define WRITE_VALUE_IDX SHA204_DATA_IDX //!< Write command index for data +#define WRITE_MAC_VS_IDX ( 9) //!< Write command index for MAC following short data +#define WRITE_MAC_VL_IDX (37) //!< Write command index for MAC following long data +#define WRITE_COUNT_SHORT (11) //!< Write command packet size with short data and no MAC +#define WRITE_COUNT_LONG (39) //!< Write command packet size with long data and no MAC +#define WRITE_COUNT_SHORT_MAC (43) //!< Write command packet size with short data and MAC +#define WRITE_COUNT_LONG_MAC (71) //!< Write command packet size with long data and MAC +#define WRITE_MAC_SIZE (32) //!< Write MAC size +#define WRITE_ZONE_MASK ((uint8_t) 0xC3) //!< Write zone bits 2 to 5 are 0. +#define WRITE_ZONE_WITH_MAC ((uint8_t) 0x40) //!< Write zone bit 6: write encrypted with MAC + +// Response size definitions +#define GENDIG_RSP_SIZE SHA204_RSP_SIZE_MIN //!< response size of GenDig command +#define HMAC_RSP_SIZE SHA204_RSP_SIZE_MAX //!< response size of HMAC command +#define NONCE_RSP_SIZE_SHORT SHA204_RSP_SIZE_MIN //!< response size of Nonce command with mode[0:1] = 3 +#define NONCE_RSP_SIZE_LONG SHA204_RSP_SIZE_MAX //!< response size of Nonce command +#define RANDOM_RSP_SIZE SHA204_RSP_SIZE_MAX //!< response size of Random command +#define WRITE_RSP_SIZE SHA204_RSP_SIZE_MIN //!< response size of Write command + +// command timing definitions for minimum execution times (ms) +#define GENDIG_DELAY ((uint8_t) (11.0 * CPU_CLOCK_DEVIATION_NEGATIVE - 0.5)) +#define HMAC_DELAY ((uint8_t) (27.0 * CPU_CLOCK_DEVIATION_NEGATIVE - 0.5)) +#define NONCE_DELAY ((uint8_t) (22.0 * CPU_CLOCK_DEVIATION_NEGATIVE - 0.5)) +#define RANDOM_DELAY ((uint8_t) (11.0 * CPU_CLOCK_DEVIATION_NEGATIVE - 0.5)) +#define WRITE_DELAY ((uint8_t) ( 4.0 * CPU_CLOCK_DEVIATION_NEGATIVE - 0.5)) + +// command timing definitions for maximum execution times (ms) +#define GENDIG_EXEC_MAX ((uint8_t) (43.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5)) +#define HMAC_EXEC_MAX ((uint8_t) (69.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5)) +#define NONCE_EXEC_MAX ((uint8_t) (60.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5)) +#define RANDOM_EXEC_MAX ((uint8_t) (50.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5)) +#define WRITE_EXEC_MAX ((uint8_t) (42.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5)) + +/* from sha204_config.h */ + +#define CPU_CLOCK_DEVIATION_POSITIVE (1.01) +#define CPU_CLOCK_DEVIATION_NEGATIVE (0.99) +#define SHA204_RETRY_COUNT (1) +#define SWI_RECEIVE_TIME_OUT ((uint16_t) 163) //! #START_PULSE_TIME_OUT in us instead of loop counts +#define SWI_US_PER_BYTE ((uint16_t) 313) //! It takes 312.5 us to send a byte (9 single-wire bits / 230400 Baud * 8 flag bits). +#define SHA204_SYNC_TIMEOUT ((uint8_t) 85)//! delay before sending a transmit flag in the synchronization routine +#define SHA204_RESPONSE_TIMEOUT ((uint16_t) SWI_RECEIVE_TIME_OUT + SWI_US_PER_BYTE) //! SWI response timeout is the sum of receive timeout and the time it takes to send the TX flag. + +/* from sha204_comm.h */ + +#define SHA204_COMMAND_EXEC_MAX ((uint8_t) (69.0 * CPU_CLOCK_DEVIATION_POSITIVE + 0.5)) //! maximum command delay +#define SHA204_CMD_SIZE_MIN ((uint8_t) 7) //! minimum number of bytes in command (from count byte to second CRC byte) +#define SHA204_CMD_SIZE_MAX ((uint8_t) NONCE_COUNT_LONG) //! maximum size of command packet (Nonce) +#define SHA204_CRC_SIZE ((uint8_t) 2) //! number of CRC bytes +#define SHA204_BUFFER_POS_STATUS (1) //! buffer index of status byte in status response +#define SHA204_BUFFER_POS_DATA (1) //! buffer index of first data byte in data response +#define SHA204_STATUS_BYTE_WAKEUP ((uint8_t) 0x11) //! command parse error +#define SHA204_STATUS_BYTE_PARSE ((uint8_t) 0x03) //! command parse error +#define SHA204_STATUS_BYTE_EXEC ((uint8_t) 0x0F) //! command execution error +#define SHA204_STATUS_BYTE_COMM ((uint8_t) 0xFF) //! communication error + +class atsha204Class +{ +private: + uint8_t device_pin; + volatile uint8_t *device_port_DDR, *device_port_OUT, *device_port_IN; + void sha204c_calculate_crc(uint8_t length, uint8_t *data, uint8_t *crc); + uint8_t sha204c_check_crc(uint8_t *response); + void swi_set_signal_pin(uint8_t is_high); + uint8_t swi_receive_bytes(uint8_t count, uint8_t *buffer); + uint8_t swi_send_bytes(uint8_t count, uint8_t *buffer); + uint8_t swi_send_byte(uint8_t value); + uint8_t sha204p_receive_response(uint8_t size, uint8_t *response); +public: + atsha204Class(uint8_t pin); // Constructor + uint8_t sha204c_wakeup(uint8_t *response); + uint8_t sha204c_sleep(); + uint8_t sha204c_send_and_receive(uint8_t *tx_buffer, uint8_t rx_size, uint8_t *rx_buffer, uint8_t execution_delay, uint8_t execution_timeout); + uint8_t sha204c_resync(uint8_t size, uint8_t *response); + uint8_t sha204m_execute(uint8_t op_code, uint8_t param1, uint16_t param2, + uint8_t datalen1, uint8_t *data1, uint8_t tx_size, uint8_t *tx_buffer, uint8_t rx_size, uint8_t *rx_buffer); +}; + +#endif From 911ae9c9d924ca879610112fa71d1b9823b17ae2 Mon Sep 17 00:00:00 2001 From: Patrick Fallberg Date: Tue, 17 Feb 2015 23:10:56 +0100 Subject: [PATCH 5/7] ATSHA204 emulated signing backend ATSHA204 emulated signing backend. The emulated ATSHA204 implementation offers pseudo random number generation and HMAC-SHA256 authentication compatible with a "physical" ATSHA204. NOTE: Key is stored in clear text in the Arduino firmware. Therefore, the use of this back-end could compromise the key used in the signed message infrastructure if device is lost and its memory dumped. The seed for the random number generated is sampled from an analog pin. The pin is set in MyConfig.h using RANDOMSEED_PIN. The pin should be unconnected. The key to use for HMAC calculations must match the key used by other nodes this node will exchange signed messages with. The key for ATSHA204 emulator is set in MyConfig.h using hmacKey. --- libraries/MySensors/MyConfig.h | 28 +++ .../MySensors/MySigningDriverAtsha204Soft.cpp | 225 ++++++++++++++++++ .../MySensors/MySigningDriverAtsha204Soft.h | 43 ++++ libraries/MySensors/utility/sha256.cpp | 167 +++++++++++++ libraries/MySensors/utility/sha256.h | 40 ++++ 5 files changed, 503 insertions(+) create mode 100644 libraries/MySensors/MySigningDriverAtsha204Soft.cpp create mode 100644 libraries/MySensors/MySigningDriverAtsha204Soft.h create mode 100644 libraries/MySensors/utility/sha256.cpp create mode 100644 libraries/MySensors/utility/sha256.h diff --git a/libraries/MySensors/MyConfig.h b/libraries/MySensors/MyConfig.h index 82c02da..0ffab4e 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -18,6 +18,7 @@ // Choose signing backend by enabling one of the following #define MYSENSORS_SIGNING_DUMMY //#define MYSENSORS_SIGNING_ATSHA204 +//#define MYSENSORS_SIGNING_ATSHA204SOFT // Define a suitable timeout for a signature verification session // Consider the turnaround from a nonce being generated to a signed message being received @@ -58,6 +59,33 @@ typedef class MySigningDriverDummy MySigningDriverClass; typedef class MySigningDriverAtsha204 MySigningDriverClass; #endif +#ifdef MYSENSORS_SIGNING_ATSHA204SOFT +// Key to use for HMAC calculation +static uint8_t hmacKey[32] = { +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; + +// Pick an unconnected analog pin for RANDOMSEED_PIN +#ifdef MYSENSORS_SENSOR +#define RANDOMSEED_PIN 7 //A7 +#endif + +#ifdef MYSENSORS_SERIAL_GATEWAY +#define RANDOMSEED_PIN 7 //A7 +#endif + +#ifdef MYSENSORS_ETHERNET_MQTT_GATEWAY +#define RANDOMSEED_PIN 7 //A7 +#endif + +#include "MySigningDriverAtsha204Soft.h" +#define SIGNING_IDENTIFIER (1) // SHA256-based HMAC (ATSHA204 specific) +typedef class MySigningDriverAtsha204Soft MySigningDriverClass; +#endif + /*** * Enable/Disable debug logging */ diff --git a/libraries/MySensors/MySigningDriverAtsha204Soft.cpp b/libraries/MySensors/MySigningDriverAtsha204Soft.cpp new file mode 100644 index 0000000..aaff71f --- /dev/null +++ b/libraries/MySensors/MySigningDriverAtsha204Soft.cpp @@ -0,0 +1,225 @@ +/* + ATSHA204 emulated signing backend. The emulated ATSHA204 implementation offers pseudo random + number generation and HMAC-SHA256 authentication compatible with a "physical" ATSHA204. + NOTE: Key is stored in clear text in the Arduino firmware. Therefore, the use of this back-end + could compromise the key used in the signed message infrastructure if device is lost and its memory + dumped. + + Created by Patrick "Anticimex" Fallberg +*/ +#include "MySigningDriver.h" +#include "MySigningDriverAtsha204Soft.h" +#include "sha256.h" + +#ifdef MYSENSORS_SIGNING_ATSHA204SOFT +// Uncomment this to get some useful serial debug info (Serial.print and Serial.println expected) +//#define DEBUG_ATSHASOFT_SIGNING + +#ifdef DEBUG_ATSHASOFT_SIGNING +#define DEBUG_ATSHASOFT_PRINTLN(args) Serial.println(args) +#else +#define DEBUG_ATSHASOFT_PRINTLN(args) +#endif + +#ifdef DEBUG_ATSHASOFT_SIGNING +static void DEBUG_ATSHASOFT_PRINTBUF(char* str, uint8_t* buf, uint8_t sz) +{ + int i; + Serial.println(str); + for (int i=0; i timestamp + VERIFICATION_TIMEOUT_MS) { + DEBUG_ATSHASOFT_PRINTLN("Verification timeout"); + // Purge nonce + memset(current_nonce, 0xAA, 32); + verification_ongoing = false; + return false; + } + } + return true; +} + +bool MySigningDriverAtsha204Soft::putNonce(MyMessage &msg) { + if (mGetLength(msg) != MAX_PAYLOAD) { + DEBUG_ATSHASOFT_PRINTLN("Incoming nonce with incorrect size"); + return false; // We require as big nonce as possible + } + + if (((uint8_t*)msg.getCustom())[0] != SIGNING_IDENTIFIER) { + DEBUG_ATSHASOFT_PRINTLN("Incorrect signing identifier"); + return false; + } + + memcpy(current_nonce, (uint8_t*)msg.getCustom(), MAX_PAYLOAD); + return true; +} + +bool MySigningDriverAtsha204Soft::signMsg(MyMessage &msg) { + // If we cannot fit any signature in the message, refuse to sign it + if (mGetLength(msg) > MAX_PAYLOAD-2) { + DEBUG_ATSHASOFT_PRINTLN("Cannot fit any signature to this message"); + return false; + } + + // Calculate signature of message + mSetSigned(msg, 1); // make sure signing flag is set before signature is calculated + DEBUG_ATSHASOFT_PRINTBUF("Message to sign:", (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg))); + if (!calculateSignature(msg)) { + DEBUG_ATSHASOFT_PRINTLN("Failed to calculate signature"); + mSetSigned(msg, 0); // make sure signing flag is truthful + return false; + } + + // Transfer as much signature data as the remaining space in the message permits + memcpy(&msg.data[mGetLength(msg)], hmac, MAX_PAYLOAD-mGetLength(msg)); + + return true; +} + +bool MySigningDriverAtsha204Soft::verifyMsg(MyMessage &msg) { + if (!verification_ongoing) { + DEBUG_ATSHASOFT_PRINTLN("No active verification session"); + return false; + } else { + // Make sure we have not expired + if (!checkTimer()) { + return false; + } + + verification_ongoing = false; + + if (msg.data[mGetLength(msg)] != SIGNING_IDENTIFIER) { + DEBUG_ATSHASOFT_PRINTLN("Incorrect signing identifier"); + return false; + } + + DEBUG_ATSHASOFT_PRINTBUF("Message to verify:", (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg))); + DEBUG_ATSHASOFT_PRINTBUF("Signature in message:", (uint8_t*)&msg.data[mGetLength(msg)], MAX_PAYLOAD-mGetLength(msg)); + + // Get signature of message + if (!calculateSignature(msg)) { + DEBUG_ATSHASOFT_PRINTLN("Failed to calculate signature"); + return false; + } + + // Compare the caluclated signature with the provided signature + if (memcmp(&msg.data[mGetLength(msg)], hmac, MAX_PAYLOAD-mGetLength(msg))) { + DEBUG_ATSHASOFT_PRINTBUF("Signature bad. Calculated signature:", hmac, MAX_PAYLOAD-mGetLength(msg)); + return false; + } else { + DEBUG_ATSHASOFT_PRINTLN("Signature ok"); + return true; + } + } +} + +// Helper to calculate signature of msg (returned in rx_buffer[SHA204_BUFFER_POS_DATA]) +bool MySigningDriverAtsha204Soft::calculateSignature(MyMessage &msg) { + memset(temp_message, 0, 32); + memcpy(temp_message, (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg))); + + // ATSHA204 calculates the HMAC with a PSK and a SHA256 digest of the following data: + // 32 bytes zeroes + // 32 bytes digest, + // 1 byte OPCODE (0x11) + // 1 byte Mode (0x04) + // 2 bytes SlotID (0x0000) + // 11 bytes zeroes + // SN[8] (0xEE) + // 4 bytes zeroes + // SN[0:1] (0x0123) + // 2 bytes zeroes + + // The digest is calculated as a SHA256 digest of the following: + // 32 bytes message + // 1 byte OPCODE (0x15) + // 1 byte param1 (0x02) + // 2 bytes param2 (0x0800) + // SN[8] (0xEE) + // SN[0:1] (0x0123) + // 25 bytes zeroes + // 32 bytes nonce + + // Calculate message digest first + Sha256.init(); + for (int i=0; i<32; i++) Sha256.write(temp_message[i]); + Sha256.write(0x15); // OPCODE + Sha256.write(0x02); // param1 + Sha256.write(0x08); // param2(1) + Sha256.write(0x00); // param2(2) + Sha256.write(0xEE); // SN[8] + Sha256.write(0x01); // SN[0] + Sha256.write(0x23); // SN[1] + for (int i=0; i<25; i++) Sha256.write(0x00); + for (int i=0; i<32; i++) Sha256.write(current_nonce[i]); + // Purge nonce when used + memset(current_nonce, 0xAA, 32); + memcpy(temp_message, Sha256.result(), 32); + + // Feed "message" to HMAC calculator + Sha256.initHmac(hmacKey,32); // Set the key to use + for (int i=0; i<32; i++) Sha256.write(0x00); // 32 bytes zeroes + for (int i=0; i<32; i++) Sha256.write(temp_message[i]); // 32 bytes digest + Sha256.write(0x11); // OPCODE + Sha256.write(0x04); // Mode + Sha256.write(0x00); // SlotID(1) + Sha256.write(0x00); // SlotID(2) + for (int i=0; i<11; i++) Sha256.write(0x00); // 11 bytes zeroes + Sha256.write(0xEE); // SN[8] + for (int i=0; i<4; i++) Sha256.write(0x00); // 4 bytes zeroes + Sha256.write(0x01); // SN[0] + Sha256.write(0x23); // SN[1] + for (int i=0; i<2; i++) Sha256.write(0x00); // 2 bytes zeroes + + hmac = Sha256.resultHmac(); + + // Overwrite the first byte in the signature with the signing identifier + hmac[0] = SIGNING_IDENTIFIER; + + DEBUG_ATSHASOFT_PRINTBUF("HMAC:", hmac, 32); + return true; +} +#endif diff --git a/libraries/MySensors/MySigningDriverAtsha204Soft.h b/libraries/MySensors/MySigningDriverAtsha204Soft.h new file mode 100644 index 0000000..16d8d3e --- /dev/null +++ b/libraries/MySensors/MySigningDriverAtsha204Soft.h @@ -0,0 +1,43 @@ +/* + ATSHA204 emulated signing backend. The emulated ATSHA204 implementation offers pseudo random + number generation and HMAC-SHA256 authentication compatible with a "physical" ATSHA204. + NOTE: Key is stored in clear text in the Arduino firmware. Therefore, the use of this back-end + could compromise the key used in the signed message infrastructure if device is lost and its memory + dumped. + + Created by Patrick "Anticimex" Fallberg +*/ +#ifndef MySigningDriverHmac256Soft_h +#define MySigningDriverHmac256Soft_h + +#include "MyConfig.h" +#include "MySigningDriver.h" +#include "utility/ATSHA204.h" +#include + +// This implementation is the pure software variant of the ATSHA204. +// It is designed to work fully compliant with nodes using ATSHA204 in the network +// and therefore uses the same signing identifier as ATSHA204. +// Because it is completly software based, the quality of the generated random numbers +// is weaker though. Random numbers are generated using the Arduino library and seed +// is sampled from an analog pin. This pin should unconnected in the hardware. +// The pin is selected using RANDOMSEED_PIN in MyConfig.h. +class MySigningDriverAtsha204Soft : public MySigningDriver +{ +public: + MySigningDriverAtsha204Soft(); + bool getNonce(MyMessage &msg); + bool checkTimer(void); + bool putNonce(MyMessage &msg); + bool signMsg(MyMessage &msg); + bool verifyMsg(MyMessage &msg); +private: + unsigned long timestamp; + bool verification_ongoing; + uint8_t current_nonce[NONCE_NUMIN_SIZE_PASSTHROUGH]; + uint8_t temp_message[32]; + uint8_t* hmac; + bool calculateSignature(MyMessage &msg); +}; + +#endif diff --git a/libraries/MySensors/utility/sha256.cpp b/libraries/MySensors/utility/sha256.cpp new file mode 100644 index 0000000..8434499 --- /dev/null +++ b/libraries/MySensors/utility/sha256.cpp @@ -0,0 +1,167 @@ +#include +#include +#include +#include "sha256.h" + +uint32_t sha256K[] PROGMEM = { + 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, + 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, + 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, + 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, + 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, + 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, + 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, + 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2 +}; + +#define BUFFER_SIZE 64 + +uint8_t sha256InitState[] PROGMEM = { + 0x67,0xe6,0x09,0x6a, // H0 + 0x85,0xae,0x67,0xbb, // H1 + 0x72,0xf3,0x6e,0x3c, // H2 + 0x3a,0xf5,0x4f,0xa5, // H3 + 0x7f,0x52,0x0e,0x51, // H4 + 0x8c,0x68,0x05,0x9b, // H5 + 0xab,0xd9,0x83,0x1f, // H6 + 0x19,0xcd,0xe0,0x5b // H7 +}; + +void Sha256Class::init(void) { + memcpy_P(state.b,sha256InitState,32); + byteCount = 0; + bufferOffset = 0; +} + +uint32_t Sha256Class::ror32(uint32_t number, uint8_t bits) { + return ((number << (32-bits)) | (number >> bits)); +} + +void Sha256Class::hashBlock() { + uint8_t i; + uint32_t a,b,c,d,e,f,g,h,t1,t2; + + a=state.w[0]; + b=state.w[1]; + c=state.w[2]; + d=state.w[3]; + e=state.w[4]; + f=state.w[5]; + g=state.w[6]; + h=state.w[7]; + + for (i=0; i<64; i++) { + if (i>=16) { + t1 = buffer.w[i&15] + buffer.w[(i-7)&15]; + t2 = buffer.w[(i-2)&15]; + t1 += ror32(t2,17) ^ ror32(t2,19) ^ (t2>>10); + t2 = buffer.w[(i-15)&15]; + t1 += ror32(t2,7) ^ ror32(t2,18) ^ (t2>>3); + buffer.w[i&15] = t1; + } + t1 = h; + t1 += ror32(e,6) ^ ror32(e,11) ^ ror32(e,25); // ∑1(e) + t1 += g ^ (e & (g ^ f)); // Ch(e,f,g) + t1 += pgm_read_dword(sha256K+i); // Ki + t1 += buffer.w[i&15]; // Wi + t2 = ror32(a,2) ^ ror32(a,13) ^ ror32(a,22); // ∑0(a) + t2 += ((b & c) | (a & (b | c))); // Maj(a,b,c) + h=g; g=f; f=e; e=d+t1; d=c; c=b; b=a; a=t1+t2; + } + state.w[0] += a; + state.w[1] += b; + state.w[2] += c; + state.w[3] += d; + state.w[4] += e; + state.w[5] += f; + state.w[6] += g; + state.w[7] += h; +} + +void Sha256Class::addUncounted(uint8_t data) { + buffer.b[bufferOffset ^ 3] = data; + bufferOffset++; + if (bufferOffset == BUFFER_SIZE) { + hashBlock(); + bufferOffset = 0; + } +} + +void Sha256Class::write(uint8_t data) { + ++byteCount; + addUncounted(data); +} + +void Sha256Class::pad() { + // Implement SHA-256 padding (fips180-2 §5.1.1) + + // Pad with 0x80 followed by 0x00 until the end of the block + addUncounted(0x80); + while (bufferOffset != 56) addUncounted(0x00); + + // Append length in the last 8 bytes + addUncounted(0); // We're only using 32 bit lengths + addUncounted(0); // But SHA-1 supports 64 bit lengths + addUncounted(0); // So zero pad the top bits + addUncounted(byteCount >> 29); // Shifting to multiply by 8 + addUncounted(byteCount >> 21); // as SHA-1 supports bitstreams as well as + addUncounted(byteCount >> 13); // byte. + addUncounted(byteCount >> 5); + addUncounted(byteCount << 3); +} + + +uint8_t* Sha256Class::result(void) { + // Pad to complete the last block + pad(); + + // Swap byte order back + for (int i=0; i<8; i++) { + uint32_t a,b; + a=state.w[i]; + b=a<<24; + b|=(a<<8) & 0x00ff0000; + b|=(a>>8) & 0x0000ff00; + b|=a>>24; + state.w[i]=b; + } + + // Return pointer to hash (20 characters) + return state.b; +} + +#define HMAC_IPAD 0x36 +#define HMAC_OPAD 0x5c + +uint8_t keyBuffer[BLOCK_LENGTH]; // K0 in FIPS-198a +uint8_t innerHash[HASH_LENGTH]; + +void Sha256Class::initHmac(const uint8_t* key, int keyLength) { + uint8_t i; + memset(keyBuffer,0,BLOCK_LENGTH); + if (keyLength > BLOCK_LENGTH) { + // Hash long keys + init(); + for (;keyLength--;) write(*key++); + memcpy(keyBuffer,result(),HASH_LENGTH); + } else { + // Block length keys are used as is + memcpy(keyBuffer,key,keyLength); + } + // Start inner hash + init(); + for (i=0; i + +#define HASH_LENGTH 32 +#define BLOCK_LENGTH 64 + +union _buffer { + uint8_t b[BLOCK_LENGTH]; + uint32_t w[BLOCK_LENGTH/4]; +}; +union _state { + uint8_t b[HASH_LENGTH]; + uint32_t w[HASH_LENGTH/4]; +}; + +class Sha256Class +{ + public: + void init(void); + void initHmac(const uint8_t* secret, int secretLength); + uint8_t* result(void); + uint8_t* resultHmac(void); + virtual void write(uint8_t); + private: + void pad(); + void addUncounted(uint8_t data); + void hashBlock(); + uint32_t ror32(uint32_t number, uint8_t bits); + _buffer buffer; + uint8_t bufferOffset; + _state state; + uint32_t byteCount; + uint8_t keyBuffer[BLOCK_LENGTH]; + uint8_t innerHash[HASH_LENGTH]; +}; + +#endif From 644eac1712d74f8f149ab57877253129a2546c6b Mon Sep 17 00:00:00 2001 From: Patrick Fallberg Date: Sat, 7 Feb 2015 13:49:08 +0100 Subject: [PATCH 6/7] Signing support implemented in the MySensors library The dummy signing driver backend is used. But with this commit all framework logic for managing signed messages in the MySensors ecosystem is implemented and enables more sophisticated signing backends to be added later on. For a sensor (or gateway) that require a gateway to send signed messages to it, add 'true' as the fifth argument to 'begin'. The gateway will automatically inform a node of it's signing requirements as soon as it receivs the nodes signing preference. It is important to understand that if the gateway require signing, it will in this implementation only require signing from nodes that require signing by the gateway. If the sensor requires signing from another node on the network which it communicates directly with, it needs to send 'true' in an internal message of type I_REQUEST_SIGNING. --- Bootloader/MyOtaBootloader.c | 6 ++ libraries/MySensors/MySensor.cpp | 99 +++++++++++++++++++++++++++++--- libraries/MySensors/MySensor.h | 13 ++++- 3 files changed, 109 insertions(+), 9 deletions(-) diff --git a/Bootloader/MyOtaBootloader.c b/Bootloader/MyOtaBootloader.c index fe4f24f..8dc1ef5 100644 --- a/Bootloader/MyOtaBootloader.c +++ b/Bootloader/MyOtaBootloader.c @@ -152,6 +152,12 @@ int main () { address(nc.nodeId); } + // Inform gateway that bootloader does not accept signed messages + msg.type = I_REQUEST_SIGNING; + mSetLength(msg, 1); + msg.data[0] = 0; + sendWrite(msg); + // Read settings from EEPROM eeprom_read_block((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(struct FirmwareConfig)); diff --git a/libraries/MySensors/MySensor.cpp b/libraries/MySensors/MySensor.cpp index 7c8bff6..c227c0c 100755 --- a/libraries/MySensors/MySensor.cpp +++ b/libraries/MySensors/MySensor.cpp @@ -13,6 +13,10 @@ #define DISTANCE_INVALID (0xFF) +// Macros for manipulating signing requirement table +#define DO_SIGN(node) (doSign[node>>4]&(node%16)) +#define SET_SIGN(node) (doSign[node>>4]|=(node%16)) +#define CLEAR_SIGN(node) (doSign[node>>4]&=~(node%16)) // Inline function and macros static inline MyMessage& build (MyMessage &msg, uint8_t sender, uint8_t destination, uint8_t sensor, uint8_t command, uint8_t type, bool enableAck) { @@ -35,14 +39,17 @@ static inline bool isValidDistance( const uint8_t distance ) { MySensor::MySensor() { radio = (MyRFDriver*) new MyRFDriverClass(); + signer = (MySigningDriver*) new MySigningDriverClass(); } -void MySensor::begin(void (*_msgCallback)(const MyMessage &), uint8_t _nodeId, boolean _repeaterMode, uint8_t _parentNodeId) { +void MySensor::begin(void (*_msgCallback)(const MyMessage &), uint8_t _nodeId, boolean _repeaterMode, uint8_t _parentNodeId, bool requestSignatures) { Serial.begin(BAUD_RATE); repeaterMode = _repeaterMode; msgCallback = _msgCallback; failedTransmissions = 0; + requireSigning = requestSignatures; + // Only gateway should use node id 0 isGateway = _nodeId == 0; @@ -55,6 +62,8 @@ void MySensor::begin(void (*_msgCallback)(const MyMessage &), uint8_t _nodeId, b eeprom_read_block((void*)&nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(NodeConfig)); // Read latest received controller configuration from EEPROM eeprom_read_block((void*)&cc, (void*)EEPROM_CONTROLLER_CONFIG_ADDRESS, sizeof(ControllerConfig)); + // Read out the signing requirements from EEPROM + eeprom_read_block((void*)doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS, sizeof(doSign)); if (isGateway) { nc.distance = 0; @@ -137,6 +146,9 @@ void MySensor::setupNode() { // Send presentation for this radio node (attach present(NODE_SENSOR_ID, repeaterMode? S_ARDUINO_REPEATER_NODE : S_ARDUINO_NODE); + // Notify gateway (and possibly controller) about the signing preferences of this node + sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_REQUEST_SIGNING, false).set(requireSigning)); + // Send a configuration exchange request to controller // Node sends parent node. Controller answers with latest node configuration // which is picked up in process() @@ -178,6 +190,19 @@ boolean MySensor::sendRoute(MyMessage &message) { return false; } + mSetVersion(message, PROTOCOL_VERSION); + + // If destination is known to require signed messages and we are the sender, sign this message unless it is an ACK or a signing handshake message + if (DO_SIGN(message.destination) && message.sender == nc.nodeId && !mGetAck(message) && mGetLength(msg) && + (mGetCommand(message) != C_INTERNAL || (message.type != I_GET_NONCE && message.type != I_GET_NONCE_RESPONSE && message.type != I_REQUEST_SIGNING))) { + if (!sign(message)) { + debug(PSTR("Message signing failed\n")); + return false; + } + // After this point, only the 'last' member of the message structure is allowed to be altered if the message has been signed, + // or signature will become invalid and the message rejected by the receiver + } else mSetSigned(message, 0); // Message is not supposed to be signed, make sure it is marked unsigned + if (dest == GATEWAY_ADDRESS || !repeaterMode) { // If destination is the gateway or if we aren't a repeater, let // our parent take care of the message @@ -240,14 +265,13 @@ boolean MySensor::sendRoute(MyMessage &message) { } boolean MySensor::sendWrite(uint8_t to, MyMessage &message) { - uint8_t length = mGetLength(message); + uint8_t length = mGetSigned(message) ? MAX_MESSAGE_LENGTH : mGetLength(message); message.last = nc.nodeId; - mSetVersion(message, PROTOCOL_VERSION); bool ok = radio->send(to, &message, min(MAX_MESSAGE_LENGTH, HEADER_SIZE + length)); - debug(PSTR("send: %d-%d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,st=%s:%s\n"), + debug(PSTR("send: %d-%d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d,st=%s:%s\n"), message.sender,message.last, to, message.destination, message.sensor, mGetCommand(message), message.type, - mGetPayloadType(message), mGetLength(message), to==BROADCAST_ADDRESS ? "bc" : (ok ? "ok":"fail"), message.getString(convBuf)); + mGetPayloadType(message), mGetLength(message), mGetSigned(message), to==BROADCAST_ADDRESS ? "bc" : (ok ? "ok":"fail"), message.getString(convBuf)); return ok; } @@ -290,12 +314,26 @@ boolean MySensor::process() { if (!radio->available(&to)) return false; + (void)signer->checkTimer(); // Manage signing timeout + uint8_t len = radio->receive((uint8_t *)&msg); + // Before processing message, reject unsigned messages if signing is required and check signature (if it is signed and addressed to us) + // Note that we do not care at all about any signature found if we do not require signing + if (requireSigning && msg.destination == nc.nodeId && mGetLength(msg) && + (mGetCommand(msg) != C_INTERNAL || (msg.type != I_GET_NONCE_RESPONSE && msg.type != I_GET_NONCE && msg.type != I_REQUEST_SIGNING))) { + if (!mGetSigned(msg)) return false; // Received an unsigned message but we do require signing. This message gets nowhere! + else if (!signer->verifyMsg(msg)) { + debug(PSTR("Message verification failed\n")); + return false; // This signed message has been tampered with! + } + } + // Add string termination, good if we later would want to print it. msg.data[mGetLength(msg)] = '\0'; - debug(PSTR("read: %d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d:%s\n"), - msg.sender, msg.last, msg.destination, msg.sensor, mGetCommand(msg), msg.type, mGetPayloadType(msg), mGetLength(msg), msg.getString(convBuf)); + debug(PSTR("read: %d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d:%s\n"), + msg.sender, msg.last, msg.destination, msg.sensor, mGetCommand(msg), msg.type, mGetPayloadType(msg), mGetLength(msg), mGetSigned(msg), msg.getString(convBuf)); + mSetSigned(msg,0); // Clear the sign-flag now as verification (and debug printing) is completed if(!(mGetVersion(msg) == PROTOCOL_VERSION)) { debug(PSTR("version: %d\n"),mGetVersion(msg)); @@ -349,6 +387,32 @@ boolean MySensor::process() { } } return false; + } else if (type == I_GET_NONCE) { + if (signer->getNonce(msg)) { + sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_GET_NONCE_RESPONSE, false)); + } else { + return false; + } + } else if (type == I_REQUEST_SIGNING) { + if (msg.getBool()) { + // We received an indicator that the sender require us to sign all messages we send to it + SET_SIGN(msg.sender); + } else { + // We received an indicator that the sender does not require us to sign all messages we send to it + CLEAR_SIGN(msg.sender); + } + // Save updated table + eeprom_write_block((void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS, (void*)doSign, sizeof(doSign)); + + // Inform sender about our preference if we are a gateway, but only require signing if the sender required signing + // We do not currently want a gateway to require signing from all nodes in a network just because it wants one node + // to sign it's messages + if (isGateway) { + if (requireSigning) + sendRoute(build(msg, nc.nodeId, msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_REQUEST_SIGNING, false).set(requireSigning)); + else + sendRoute(build(msg, nc.nodeId, msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_REQUEST_SIGNING, false).set(false)); + } } else if (sender == GATEWAY_ADDRESS) { bool isMetric; @@ -543,6 +607,27 @@ int8_t MySensor::sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, ui return retVal; } +bool MySensor::sign(MyMessage &message) { + MyMessage msgNonce; + if (!sendRoute(build(msgNonce, nc.nodeId, message.destination, message.sensor, C_INTERNAL, I_GET_NONCE, false).set(""))) { + return false; + } else { + // We have to wait for the nonce to arrive before we can sign our original message + // Other messages could come in-between. We trust process() takes care of them + unsigned long enter = millis(); + while (millis() - enter < 5000) { + if (process()) { + if (getLastMessage().type == I_GET_NONCE_RESPONSE) { + // Proceed with signing if nonce has been received + if (signer->putNonce(getLastMessage()) && signer->signMsg(message)) return true; + break; + } + } + } + } + return false; +} + #ifdef DEBUG void MySensor::debugPrint(const char *fmt, ... ) { char fmtBuffer[300]; diff --git a/libraries/MySensors/MySensor.h b/libraries/MySensors/MySensor.h index c9e79a7..8541492 100644 --- a/libraries/MySensors/MySensor.h +++ b/libraries/MySensors/MySensor.h @@ -14,6 +14,7 @@ #include "Version.h" // Auto generated by bot #include "MyRFDriver.h" +#include "MySigningDriver.h" #include "MyConfig.h" #include "MyMessage.h" #include @@ -50,7 +51,8 @@ #define EEPROM_FIRMWARE_VERSION_ADDRESS (EEPROM_FIRMWARE_TYPE_ADDRESS+2) #define EEPROM_FIRMWARE_BLOCKS_ADDRESS (EEPROM_FIRMWARE_VERSION_ADDRESS+2) #define EEPROM_FIRMWARE_CRC_ADDRESS (EEPROM_FIRMWARE_BLOCKS_ADDRESS+2) -#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_FIRMWARE_CRC_ADDRESS+2) // First free address for sketch static configuration +#define EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS (EEPROM_FIRMWARE_CRC_ADDRESS+2) +#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS+32) // First free address for sketch static configuration // Search for a new parent node after this many transmission failures #define SEARCH_FAILURES 5 @@ -86,9 +88,10 @@ class MySensor * @param nodeId The unique id (1-254) for this sensor. Default is AUTO(255) which means sensor tries to fetch an id from controller. * @param repeaterMode Activate repeater mode. This node will forward messages to other nodes in the radio network. Make sure to call process() regularly. Default in false * @param parentNodeId Use this to force node to always communicate with a certain parent node. Default is AUTO which means node automatically tries to find a parent. + * @param requestSignatures Set this to true if you want destination node to sign all messages sent to this node. Default is not to require signing. */ - void begin(void (* msgCallback)(const MyMessage &)=NULL, uint8_t nodeId=AUTO, boolean repeaterMode=false, uint8_t parentNodeId=AUTO); + void begin(void (* msgCallback)(const MyMessage &)=NULL, uint8_t nodeId=AUTO, boolean repeaterMode=false, uint8_t parentNodeId=AUTO, bool requestSignatures=false); /** * Return the nodes nodeId. @@ -238,10 +241,15 @@ class MySensor bool repeaterMode; bool autoFindParent; bool isGateway; + bool requireSigning; + uint16_t doSign[16]; // Bitfield indicating which sensors require signed communication + MyMessage msg; // Buffer for incoming messages. MyMessage ack; // Buffer for ack messages. MyRFDriver *radio; + + MySigningDriver *signer; void setupRepeaterMode(); void setupRadio(); @@ -264,6 +272,7 @@ class MySensor void addChildRoute(uint8_t childId, uint8_t route); void removeChildRoute(uint8_t childId); void internalSleep(unsigned long ms); + bool sign(MyMessage &message); }; #endif From cfc8d9f28a9c74e0854c4e41cf39d4f488c13364 Mon Sep 17 00:00:00 2001 From: Patrick Fallberg Date: Sat, 7 Feb 2015 14:35:19 +0100 Subject: [PATCH 7/7] Added example sketch for a secure actuator The sketch closely resembles the RelayActuator example. The difference is that this sketch require gateway to sign all messages addressed to the actuator. --- .../SecureActuator/SecureActuator.ino | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 libraries/MySensors/examples/SecureActuator/SecureActuator.ino diff --git a/libraries/MySensors/examples/SecureActuator/SecureActuator.ino b/libraries/MySensors/examples/SecureActuator/SecureActuator.ino new file mode 100644 index 0000000..c86ff49 --- /dev/null +++ b/libraries/MySensors/examples/SecureActuator/SecureActuator.ino @@ -0,0 +1,57 @@ +// Example sketch showing how to securely control locks. +// This example will remember lock state even after power failure. + +#include +#include + +#ifndef MYSENSORS_SENSOR +#error Please switch to MYSENSORS_SENSOR in MyConfig.h +#endif + +#define LOCK_1 3 // Arduino Digital I/O pin number for first lock (second on pin+1 etc) +#define NOF_LOCKS 1 // Total number of attached locks +#define LOCK_LOCK 1 // GPIO value to write to lock attached lock +#define LOCK_UNLOCK 0 // GPIO value to write to unlock attached lock + +MySensor gw; + +void setup() +{ + // Initialize library and add callback for incoming messages (signing is required) + gw.begin(incomingMessage, AUTO, true, AUTO, true); + // Send the sketch version information to the gateway and Controller + gw.sendSketchInfo("Secure Lock", "1.0"); + + // Fetch lock status + for (int lock=1, pin=LOCK_1; lock<=NOF_LOCKS;lock++, pin++) { + // Register all locks to gw (they will be created as child devices) + gw.present(lock, S_LOCK, false); + // Then set lock pins in output mode + pinMode(pin, OUTPUT); + // Set lock to last known state (using eeprom storage) + digitalWrite(pin, gw.loadState(lock)?LOCK_LOCK:LOCK_UNLOCK); + } +} + + +void loop() +{ + // Alway process incoming messages whenever possible + gw.process(); +} + +void incomingMessage(const MyMessage &message) { + // We only expect one type of message from controller. But we better check anyway. + if (message.type==V_LOCK_STATUS && message.sensor<=NOF_LOCKS) { + // Change relay state + digitalWrite(message.sensor-1+LOCK_1, message.getBool()?LOCK_LOCK:LOCK_UNLOCK); + // Store state in eeprom + gw.saveState(message.sensor, message.getBool()); + // Write some debug info + Serial.print("Incoming change for lock:"); + Serial.print(message.sensor); + Serial.print(", New status: "); + Serial.println(message.getBool()); + } +} +