Skip to content

Commit 11058f7

Browse files
committed
Merge pull request arduino#71 from fallberg/sign_dev
Signing support for MySensors
2 parents 8b8f623 + cfc8d9f commit 11058f7

18 files changed

+1932
-15
lines changed

Bootloader/MyOtaBootloader.c

+6
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ int main () {
152152
address(nc.nodeId);
153153
}
154154

155+
// Inform gateway that bootloader does not accept signed messages
156+
msg.type = I_REQUEST_SIGNING;
157+
mSetLength(msg, 1);
158+
msg.data[0] = 0;
159+
sendWrite(msg);
160+
155161
// Read settings from EEPROM
156162
eeprom_read_block((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(struct FirmwareConfig));
157163

libraries/MySensors/MyConfig.h

+59-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@
1515
#define MYSENSORS_RF_NRF24
1616
//#define MYSENSORS_RF_RF69
1717

18+
// Choose signing backend by enabling one of the following
19+
#define MYSENSORS_SIGNING_DUMMY
20+
//#define MYSENSORS_SIGNING_ATSHA204
21+
//#define MYSENSORS_SIGNING_ATSHA204SOFT
1822

19-
20-
23+
// Define a suitable timeout for a signature verification session
24+
// Consider the turnaround from a nonce being generated to a signed message being received
25+
// which might vary, especially in networks with many hops. 5s ought to be enough for anyone.
26+
#define VERIFICATION_TIMEOUT_MS 5000
2127

2228
#ifdef MYSENSORS_RF_NRF24
2329
#include "MyRFDriverNRF24.h"
@@ -29,6 +35,57 @@ typedef class MyRFDriverNRF24 MyRFDriverClass;
2935
typedef class MyRFDriverRF69 MyRFDriverClass;
3036
#endif
3137

38+
#ifdef MYSENSORS_SIGNING_DUMMY
39+
#include "MySigningDriverDummy.h"
40+
#define SIGNING_IDENTIFIER (0) // Reserved for dummy implementation, will not work with other backends
41+
typedef class MySigningDriverDummy MySigningDriverClass;
42+
#endif
43+
44+
#ifdef MYSENSORS_SIGNING_ATSHA204
45+
#ifdef MYSENSORS_SENSOR
46+
#define ATSHA204_PIN 17 //A3
47+
#endif
48+
49+
#ifdef MYSENSORS_SERIAL_GATEWAY
50+
#define ATSHA204_PIN 17 //A3
51+
#endif
52+
53+
#ifdef MYSENSORS_ETHERNET_MQTT_GATEWAY
54+
#define ATSHA204_PIN 17 //A3
55+
#endif
56+
57+
#include "MySigningDriverAtsha204.h"
58+
#define SIGNING_IDENTIFIER (1) // SHA256-based HMAC (ATSHA204 specific)
59+
typedef class MySigningDriverAtsha204 MySigningDriverClass;
60+
#endif
61+
62+
#ifdef MYSENSORS_SIGNING_ATSHA204SOFT
63+
// Key to use for HMAC calculation
64+
static uint8_t hmacKey[32] = {
65+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
66+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
67+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
68+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
69+
};
70+
71+
// Pick an unconnected analog pin for RANDOMSEED_PIN
72+
#ifdef MYSENSORS_SENSOR
73+
#define RANDOMSEED_PIN 7 //A7
74+
#endif
75+
76+
#ifdef MYSENSORS_SERIAL_GATEWAY
77+
#define RANDOMSEED_PIN 7 //A7
78+
#endif
79+
80+
#ifdef MYSENSORS_ETHERNET_MQTT_GATEWAY
81+
#define RANDOMSEED_PIN 7 //A7
82+
#endif
83+
84+
#include "MySigningDriverAtsha204Soft.h"
85+
#define SIGNING_IDENTIFIER (1) // SHA256-based HMAC (ATSHA204 specific)
86+
typedef class MySigningDriverAtsha204Soft MySigningDriverClass;
87+
#endif
88+
3289
/***
3390
* Enable/Disable debug logging
3491
*/

libraries/MySensors/MyMessage.h

+8-4
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ typedef enum {
4747
I_BATTERY_LEVEL, I_TIME, I_VERSION, I_ID_REQUEST, I_ID_RESPONSE,
4848
I_INCLUSION_MODE, I_CONFIG, I_FIND_PARENT, I_FIND_PARENT_RESPONSE,
4949
I_LOG_MESSAGE, I_CHILDREN, I_SKETCH_NAME, I_SKETCH_VERSION,
50-
I_REBOOT, I_GATEWAY_READY
50+
I_REBOOT, I_GATEWAY_READY, I_REQUEST_SIGNING, I_GET_NONCE, I_GET_NONCE_RESPONSE
5151
} mysensor_internal;
5252

5353
// Type of sensor (for presentation message)
@@ -84,8 +84,11 @@ typedef enum {
8484
#define BF_SET(y, x, start, len) ( y= ((y) &~ BF_MASK(start, len)) | BF_PREP(x, start, len) )
8585

8686
// Getters/setters for special bit fields in header
87-
#define mSetVersion(_msg,_version) BF_SET(_msg.version_length, _version, 0, 3)
88-
#define mGetVersion(_msg) BF_GET(_msg.version_length, 0, 3)
87+
#define mSetVersion(_msg,_version) BF_SET(_msg.version_length, _version, 0, 2)
88+
#define mGetVersion(_msg) BF_GET(_msg.version_length, 0, 2)
89+
90+
#define mSetSigned(_msg,_signed) BF_SET(_msg.version_length, _signed, 2, 1)
91+
#define mGetSigned(_msg) BF_GET(_msg.version_length, 2, 1)
8992

9093
#define mSetLength(_msg,_length) BF_SET(_msg.version_length, _length, 3, 5)
9194
#define mGetLength(_msg) BF_GET(_msg.version_length, 3, 5)
@@ -179,7 +182,8 @@ struct
179182
uint8_t sender; // 8 bit - Id of sender node (origin)
180183
uint8_t destination; // 8 bit - Id of destination node
181184

182-
uint8_t version_length; // 3 bit - Protocol version
185+
uint8_t version_length; // 2 bit - Protocol version
186+
// 1 bit - Signed flag
183187
// 5 bit - Length of payload
184188
uint8_t command_ack_payload; // 3 bit - Command type
185189
// 1 bit - Request an ack - Indicator that receiver should send an ack back.

libraries/MySensors/MySensor.cpp

+92-7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313

1414
#define DISTANCE_INVALID (0xFF)
1515

16+
// Macros for manipulating signing requirement table
17+
#define DO_SIGN(node) (doSign[node>>4]&(node%16))
18+
#define SET_SIGN(node) (doSign[node>>4]|=(node%16))
19+
#define CLEAR_SIGN(node) (doSign[node>>4]&=~(node%16))
1620

1721
// Inline function and macros
1822
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 ) {
3539

3640
MySensor::MySensor() {
3741
radio = (MyRFDriver*) new MyRFDriverClass();
42+
signer = (MySigningDriver*) new MySigningDriverClass();
3843
}
3944

4045

41-
void MySensor::begin(void (*_msgCallback)(const MyMessage &), uint8_t _nodeId, boolean _repeaterMode, uint8_t _parentNodeId) {
46+
void MySensor::begin(void (*_msgCallback)(const MyMessage &), uint8_t _nodeId, boolean _repeaterMode, uint8_t _parentNodeId, bool requestSignatures) {
4247
Serial.begin(BAUD_RATE);
4348
repeaterMode = _repeaterMode;
4449
msgCallback = _msgCallback;
4550
failedTransmissions = 0;
51+
requireSigning = requestSignatures;
52+
4653
// Only gateway should use node id 0
4754
isGateway = _nodeId == 0;
4855

@@ -55,6 +62,8 @@ void MySensor::begin(void (*_msgCallback)(const MyMessage &), uint8_t _nodeId, b
5562
eeprom_read_block((void*)&nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(NodeConfig));
5663
// Read latest received controller configuration from EEPROM
5764
eeprom_read_block((void*)&cc, (void*)EEPROM_CONTROLLER_CONFIG_ADDRESS, sizeof(ControllerConfig));
65+
// Read out the signing requirements from EEPROM
66+
eeprom_read_block((void*)doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS, sizeof(doSign));
5867

5968
if (isGateway) {
6069
nc.distance = 0;
@@ -137,6 +146,9 @@ void MySensor::setupNode() {
137146
// Send presentation for this radio node (attach
138147
present(NODE_SENSOR_ID, repeaterMode? S_ARDUINO_REPEATER_NODE : S_ARDUINO_NODE);
139148

149+
// Notify gateway (and possibly controller) about the signing preferences of this node
150+
sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_REQUEST_SIGNING, false).set(requireSigning));
151+
140152
// Send a configuration exchange request to controller
141153
// Node sends parent node. Controller answers with latest node configuration
142154
// which is picked up in process()
@@ -178,6 +190,19 @@ boolean MySensor::sendRoute(MyMessage &message) {
178190
return false;
179191
}
180192

193+
mSetVersion(message, PROTOCOL_VERSION);
194+
195+
// 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
196+
if (DO_SIGN(message.destination) && message.sender == nc.nodeId && !mGetAck(message) && mGetLength(msg) &&
197+
(mGetCommand(message) != C_INTERNAL || (message.type != I_GET_NONCE && message.type != I_GET_NONCE_RESPONSE && message.type != I_REQUEST_SIGNING))) {
198+
if (!sign(message)) {
199+
debug(PSTR("Message signing failed\n"));
200+
return false;
201+
}
202+
// After this point, only the 'last' member of the message structure is allowed to be altered if the message has been signed,
203+
// or signature will become invalid and the message rejected by the receiver
204+
} else mSetSigned(message, 0); // Message is not supposed to be signed, make sure it is marked unsigned
205+
181206
if (dest == GATEWAY_ADDRESS || !repeaterMode) {
182207
// If destination is the gateway or if we aren't a repeater, let
183208
// our parent take care of the message
@@ -240,14 +265,13 @@ boolean MySensor::sendRoute(MyMessage &message) {
240265
}
241266

242267
boolean MySensor::sendWrite(uint8_t to, MyMessage &message) {
243-
uint8_t length = mGetLength(message);
268+
uint8_t length = mGetSigned(message) ? MAX_MESSAGE_LENGTH : mGetLength(message);
244269
message.last = nc.nodeId;
245-
mSetVersion(message, PROTOCOL_VERSION);
246270
bool ok = radio->send(to, &message, min(MAX_MESSAGE_LENGTH, HEADER_SIZE + length));
247271

248-
debug(PSTR("send: %d-%d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,st=%s:%s\n"),
272+
debug(PSTR("send: %d-%d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d,st=%s:%s\n"),
249273
message.sender,message.last, to, message.destination, message.sensor, mGetCommand(message), message.type,
250-
mGetPayloadType(message), mGetLength(message), to==BROADCAST_ADDRESS ? "bc" : (ok ? "ok":"fail"), message.getString(convBuf));
274+
mGetPayloadType(message), mGetLength(message), mGetSigned(message), to==BROADCAST_ADDRESS ? "bc" : (ok ? "ok":"fail"), message.getString(convBuf));
251275

252276
return ok;
253277
}
@@ -290,12 +314,26 @@ boolean MySensor::process() {
290314
if (!radio->available(&to))
291315
return false;
292316

317+
(void)signer->checkTimer(); // Manage signing timeout
318+
293319
uint8_t len = radio->receive((uint8_t *)&msg);
294320

321+
// Before processing message, reject unsigned messages if signing is required and check signature (if it is signed and addressed to us)
322+
// Note that we do not care at all about any signature found if we do not require signing
323+
if (requireSigning && msg.destination == nc.nodeId && mGetLength(msg) &&
324+
(mGetCommand(msg) != C_INTERNAL || (msg.type != I_GET_NONCE_RESPONSE && msg.type != I_GET_NONCE && msg.type != I_REQUEST_SIGNING))) {
325+
if (!mGetSigned(msg)) return false; // Received an unsigned message but we do require signing. This message gets nowhere!
326+
else if (!signer->verifyMsg(msg)) {
327+
debug(PSTR("Message verification failed\n"));
328+
return false; // This signed message has been tampered with!
329+
}
330+
}
331+
295332
// Add string termination, good if we later would want to print it.
296333
msg.data[mGetLength(msg)] = '\0';
297-
debug(PSTR("read: %d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d:%s\n"),
298-
msg.sender, msg.last, msg.destination, msg.sensor, mGetCommand(msg), msg.type, mGetPayloadType(msg), mGetLength(msg), msg.getString(convBuf));
334+
debug(PSTR("read: %d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d:%s\n"),
335+
msg.sender, msg.last, msg.destination, msg.sensor, mGetCommand(msg), msg.type, mGetPayloadType(msg), mGetLength(msg), mGetSigned(msg), msg.getString(convBuf));
336+
mSetSigned(msg,0); // Clear the sign-flag now as verification (and debug printing) is completed
299337

300338
if(!(mGetVersion(msg) == PROTOCOL_VERSION)) {
301339
debug(PSTR("version: %d\n"),mGetVersion(msg));
@@ -349,6 +387,32 @@ boolean MySensor::process() {
349387
}
350388
}
351389
return false;
390+
} else if (type == I_GET_NONCE) {
391+
if (signer->getNonce(msg)) {
392+
sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_GET_NONCE_RESPONSE, false));
393+
} else {
394+
return false;
395+
}
396+
} else if (type == I_REQUEST_SIGNING) {
397+
if (msg.getBool()) {
398+
// We received an indicator that the sender require us to sign all messages we send to it
399+
SET_SIGN(msg.sender);
400+
} else {
401+
// We received an indicator that the sender does not require us to sign all messages we send to it
402+
CLEAR_SIGN(msg.sender);
403+
}
404+
// Save updated table
405+
eeprom_write_block((void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS, (void*)doSign, sizeof(doSign));
406+
407+
// Inform sender about our preference if we are a gateway, but only require signing if the sender required signing
408+
// We do not currently want a gateway to require signing from all nodes in a network just because it wants one node
409+
// to sign it's messages
410+
if (isGateway) {
411+
if (requireSigning)
412+
sendRoute(build(msg, nc.nodeId, msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_REQUEST_SIGNING, false).set(requireSigning));
413+
else
414+
sendRoute(build(msg, nc.nodeId, msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_REQUEST_SIGNING, false).set(false));
415+
}
352416
} else if (sender == GATEWAY_ADDRESS) {
353417
bool isMetric;
354418

@@ -543,6 +607,27 @@ int8_t MySensor::sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, ui
543607
return retVal;
544608
}
545609

610+
bool MySensor::sign(MyMessage &message) {
611+
MyMessage msgNonce;
612+
if (!sendRoute(build(msgNonce, nc.nodeId, message.destination, message.sensor, C_INTERNAL, I_GET_NONCE, false).set(""))) {
613+
return false;
614+
} else {
615+
// We have to wait for the nonce to arrive before we can sign our original message
616+
// Other messages could come in-between. We trust process() takes care of them
617+
unsigned long enter = millis();
618+
while (millis() - enter < 5000) {
619+
if (process()) {
620+
if (getLastMessage().type == I_GET_NONCE_RESPONSE) {
621+
// Proceed with signing if nonce has been received
622+
if (signer->putNonce(getLastMessage()) && signer->signMsg(message)) return true;
623+
break;
624+
}
625+
}
626+
}
627+
}
628+
return false;
629+
}
630+
546631
#ifdef DEBUG
547632
void MySensor::debugPrint(const char *fmt, ... ) {
548633
char fmtBuffer[300];

libraries/MySensors/MySensor.h

+11-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include "Version.h" // Auto generated by bot
1616
#include "MyRFDriver.h"
17+
#include "MySigningDriver.h"
1718
#include "MyConfig.h"
1819
#include "MyMessage.h"
1920
#include <stddef.h>
@@ -50,7 +51,8 @@
5051
#define EEPROM_FIRMWARE_VERSION_ADDRESS (EEPROM_FIRMWARE_TYPE_ADDRESS+2)
5152
#define EEPROM_FIRMWARE_BLOCKS_ADDRESS (EEPROM_FIRMWARE_VERSION_ADDRESS+2)
5253
#define EEPROM_FIRMWARE_CRC_ADDRESS (EEPROM_FIRMWARE_BLOCKS_ADDRESS+2)
53-
#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_FIRMWARE_CRC_ADDRESS+2) // First free address for sketch static configuration
54+
#define EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS (EEPROM_FIRMWARE_CRC_ADDRESS+2)
55+
#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS+32) // First free address for sketch static configuration
5456

5557
// Search for a new parent node after this many transmission failures
5658
#define SEARCH_FAILURES 5
@@ -86,9 +88,10 @@ class MySensor
8688
* @param nodeId The unique id (1-254) for this sensor. Default is AUTO(255) which means sensor tries to fetch an id from controller.
8789
* @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
8890
* @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.
91+
* @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.
8992
*/
9093

91-
void begin(void (* msgCallback)(const MyMessage &)=NULL, uint8_t nodeId=AUTO, boolean repeaterMode=false, uint8_t parentNodeId=AUTO);
94+
void begin(void (* msgCallback)(const MyMessage &)=NULL, uint8_t nodeId=AUTO, boolean repeaterMode=false, uint8_t parentNodeId=AUTO, bool requestSignatures=false);
9295

9396
/**
9497
* Return the nodes nodeId.
@@ -238,10 +241,15 @@ class MySensor
238241
bool repeaterMode;
239242
bool autoFindParent;
240243
bool isGateway;
244+
bool requireSigning;
245+
uint16_t doSign[16]; // Bitfield indicating which sensors require signed communication
246+
241247
MyMessage msg; // Buffer for incoming messages.
242248
MyMessage ack; // Buffer for ack messages.
243249

244250
MyRFDriver *radio;
251+
252+
MySigningDriver *signer;
245253

246254
void setupRepeaterMode();
247255
void setupRadio();
@@ -264,6 +272,7 @@ class MySensor
264272
void addChildRoute(uint8_t childId, uint8_t route);
265273
void removeChildRoute(uint8_t childId);
266274
void internalSleep(unsigned long ms);
275+
bool sign(MyMessage &message);
267276
};
268277
#endif
269278

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#include "MySigningDriver.h"
2+
3+
MySigningDriver::MySigningDriver() {
4+
}

0 commit comments

Comments
 (0)