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.
This commit is contained in:
Patrick Fallberg
2015-02-17 23:25:33 +01:00
parent ce290e280e
commit 911ae9c9d9
5 changed files with 503 additions and 0 deletions
+28
View File
@@ -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
*/
@@ -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 <patrick@fallberg.net>
*/
#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<sz; i++)
{
if (buf[i] < 0x10)
{
Serial.print('0'); // Because Serial.print does not 0-pad HEX
}
Serial.print(buf[i], HEX);
}
Serial.println();
}
#else
#define DEBUG_ATSHASOFT_PRINTBUF(str, buf, sz)
#endif
static Sha256Class Sha256;
MySigningDriverAtsha204Soft::MySigningDriverAtsha204Soft() : MySigningDriver() {
verification_ongoing = false;
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
memset(current_nonce, 0xAA, sizeof(current_nonce));
}
bool MySigningDriverAtsha204Soft::getNonce(MyMessage &msg) {
// Set randomseed
randomSeed(analogRead(RANDOMSEED_PIN));
for (int i = 0; i < MAX_PAYLOAD; i++)
{
current_nonce[i] = random(255);
}
current_nonce[0] = SIGNING_IDENTIFIER;
// Transfer the first part of the nonce to the message
msg.set(current_nonce, MAX_PAYLOAD);
verification_ongoing = true;
timestamp = millis(); // Set timestamp to determine when to purge nonce
// Be a little fancy to handle turnover (prolong the time allowed to timeout after turnover)
// Note that if message is "too" quick, and arrives before turnover, it will be rejected
// but this is consider such a rare case that it is accepted and rejects are 'safe'
if (timestamp + VERIFICATION_TIMEOUT_MS < millis()) timestamp = 0;
return true;
}
bool MySigningDriverAtsha204Soft::checkTimer() {
if (verification_ongoing) {
if (millis() < timestamp || millis() > 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
@@ -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 <patrick@fallberg.net>
*/
#ifndef MySigningDriverHmac256Soft_h
#define MySigningDriverHmac256Soft_h
#include "MyConfig.h"
#include "MySigningDriver.h"
#include "utility/ATSHA204.h"
#include <stdint.h>
// 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
+167
View File
@@ -0,0 +1,167 @@
#include <string.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#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<BLOCK_LENGTH; i++) {
write(keyBuffer[i] ^ HMAC_IPAD);
}
}
uint8_t* Sha256Class::resultHmac(void) {
uint8_t i;
// Complete inner hash
memcpy(innerHash,result(),HASH_LENGTH);
// Calculate outer hash
init();
for (i=0; i<BLOCK_LENGTH; i++) write(keyBuffer[i] ^ HMAC_OPAD);
for (i=0; i<HASH_LENGTH; i++) write(innerHash[i]);
return result();
}
+40
View File
@@ -0,0 +1,40 @@
// This code is taken from https://github.com/Cathedrow/Cryptosuite (Peter Knight) and is lightly modified for MySensors use
#ifndef Sha256_h
#define Sha256_h
#include <inttypes.h>
#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