Skip to content

Commit 644eac1

Browse files
committed
Signing support implemented in the MySensors library
The dummy signing driver backend is used. But with this commit all framework logic for managing signed messages in the MySensors ecosystem is implemented and enables more sophisticated signing backends to be added later on. For a sensor (or gateway) that require a gateway to send signed messages to it, add 'true' as the fifth argument to 'begin'. The gateway will automatically inform a node of it's signing requirements as soon as it receivs the nodes signing preference. It is important to understand that if the gateway require signing, it will in this implementation only require signing from nodes that require signing by the gateway. If the sensor requires signing from another node on the network which it communicates directly with, it needs to send 'true' in an internal message of type I_REQUEST_SIGNING.
1 parent 911ae9c commit 644eac1

File tree

3 files changed

+109
-9
lines changed

3 files changed

+109
-9
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/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

0 commit comments

Comments
 (0)