From 911ae9c9d924ca879610112fa71d1b9823b17ae2 Mon Sep 17 00:00:00 2001 From: Patrick Fallberg Date: Tue, 17 Feb 2015 23:10:56 +0100 Subject: [PATCH] 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