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/MyConfig.h b/libraries/MySensors/MyConfig.h index 96f2253..0ffab4e 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -15,9 +15,15 @@ #define MYSENSORS_RF_NRF24 //#define MYSENSORS_RF_RF69 +// 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 +// 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 +35,57 @@ 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 + +#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 + +#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/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. 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 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 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/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/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 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()); + } +} + 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 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