Merge pull request #71 from fallberg/sign_dev
Signing support for MySensors
This commit is contained in:
@@ -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));
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "Version.h" // Auto generated by bot
|
||||
#include "MyRFDriver.h"
|
||||
#include "MySigningDriver.h"
|
||||
#include "MyConfig.h"
|
||||
#include "MyMessage.h"
|
||||
#include <stddef.h>
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#include "MySigningDriver.h"
|
||||
|
||||
MySigningDriver::MySigningDriver() {
|
||||
}
|
||||
@@ -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 <patrick@fallberg.net>
|
||||
*/
|
||||
#ifndef MySigningDriver_h
|
||||
#define MySigningDriver_h
|
||||
|
||||
#include <stdint.h>
|
||||
#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
|
||||
@@ -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 <patrick@fallberg.net>
|
||||
*/
|
||||
#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<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_ATSHA_PRINTBUF(str, buf, sz)
|
||||
#endif
|
||||
|
||||
static atsha204Class atsha204(ATSHA204_PIN);
|
||||
|
||||
MySigningDriverAtsha204::MySigningDriverAtsha204() : 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 MySigningDriverAtsha204::getNonce(MyMessage &msg) {
|
||||
// Wake device before starting operations
|
||||
if (atsha204.sha204c_wakeup(rx_buffer) != SHA204_SUCCESS) {
|
||||
DEBUG_ATSHA_PRINTLN("Failed to wake device");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate random number for use as nonce
|
||||
if (atsha204.sha204m_execute(SHA204_RANDOM, RANDOM_NO_SEED_UPDATE, 0, 0, NULL,
|
||||
RANDOM_COUNT, tx_buffer, RANDOM_RSP_SIZE, rx_buffer) != SHA204_SUCCESS) {
|
||||
DEBUG_ATSHA_PRINTLN("Failed to generate nonce");
|
||||
return false;
|
||||
}
|
||||
|
||||
// We can put the device back to sleep as we will program the nonce once we do the verification
|
||||
atsha204.sha204c_sleep();
|
||||
|
||||
// Store nonce and replace the first byte in the nonce with our signing identifier
|
||||
memcpy(current_nonce, &rx_buffer[SHA204_BUFFER_POS_DATA], MAX_PAYLOAD);
|
||||
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 MySigningDriverAtsha204::checkTimer() {
|
||||
if (verification_ongoing) {
|
||||
if (millis() < timestamp || millis() > 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
|
||||
@@ -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 <patrick@fallberg.net>
|
||||
*/
|
||||
#ifndef MySigningDriverAtsha204_h
|
||||
#define MySigningDriverAtsha204_h
|
||||
|
||||
#include "MyConfig.h"
|
||||
#include "MySigningDriver.h"
|
||||
#include "utility/ATSHA204.h"
|
||||
#include <stdint.h>
|
||||
|
||||
// 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
|
||||
@@ -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
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
Dummy signing backend. Does not provide any security measures.
|
||||
|
||||
Created by Patrick "Anticimex" Fallberg <patrick@fallberg.net>
|
||||
*/
|
||||
#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<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_DUMMY_PRINTBUF(str, buf, sz)
|
||||
#endif
|
||||
|
||||
MySigningDriverDummy::MySigningDriverDummy() : MySigningDriver() {
|
||||
verification_ongoing = false;
|
||||
current_nonce = 0;
|
||||
}
|
||||
|
||||
bool MySigningDriverDummy::getNonce(MyMessage &msg) {
|
||||
uint8_t nonce_buf[2];
|
||||
nonce_buf[0] = SIGNING_IDENTIFIER;
|
||||
// This is normally a buffer but for our minimal driver we use a single byte
|
||||
// We at least use a new nonce for every session
|
||||
nonce_buf[1] = ++current_nonce;
|
||||
msg.set(nonce_buf, 2);
|
||||
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 MySigningDriverDummy::checkTimer() {
|
||||
if (verification_ongoing) {
|
||||
if (millis() < timestamp || millis() > 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
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Dummy signing backend. Does not provide any security measures.
|
||||
|
||||
Created by Patrick "Anticimex" Fallberg <patrick@fallberg.net>
|
||||
*/
|
||||
#ifndef MySigningDriverDummy_h
|
||||
#define MySigningDriverDummy_h
|
||||
|
||||
#include "MyConfig.h"
|
||||
#include "MySigningDriver.h"
|
||||
#include <stdint.h>
|
||||
|
||||
// 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
|
||||
@@ -0,0 +1,57 @@
|
||||
// Example sketch showing how to securely control locks.
|
||||
// This example will remember lock state even after power failure.
|
||||
|
||||
#include <MySensor.h>
|
||||
#include <SPI.h>
|
||||
|
||||
#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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user