Skip to content

Commit 02fbe53

Browse files
committed
Signing infrastructure refactored
Signing logic has now been centralized to MySigning.cpp in order to simplify maintenance and debugging. Debug prints have been extended and "demoted" to verbose level. To get signing related debug info, MY_DEBUG_VERBOSE will need to be defined. This fixes arduino#278
1 parent b64f676 commit 02fbe53

11 files changed

+394
-214
lines changed

libraries/MySensors/MySensor.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,11 @@
9595

9696
// SIGNING
9797
#if defined(MY_SIGNING_ATSHA204) || defined(MY_SIGNING_SOFT)
98-
// SIGNING COMMON FUNCTIONS
99-
#include "core/MySigning.cpp"
10098
#define MY_SIGNING_FEATURE
99+
#endif
100+
#include "core/MySigning.cpp"
101+
#if defined(MY_SIGNING_FEATURE)
102+
// SIGNING COMMON FUNCTIONS
101103
#if defined(MY_SIGNING_ATSHA204) && defined(MY_SIGNING_SOFT)
102104
#error Only one signing engine can be activated
103105
#endif

libraries/MySensors/core/MyMessage.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,9 @@ typedef enum {
163163
I_INCLUSION_MODE, I_CONFIG, I_FIND_PARENT, I_FIND_PARENT_RESPONSE,
164164
I_LOG_MESSAGE, I_CHILDREN, I_SKETCH_NAME, I_SKETCH_VERSION,
165165
I_REBOOT, I_GATEWAY_READY,
166-
I_REQUEST_SIGNING, /*!< Indicates sender require signed messages (bool param) */
167-
I_GET_NONCE, /*!< Request for a nonce */
168-
I_GET_NONCE_RESPONSE, /*!< Payload is nonce data */
166+
I_REQUEST_SIGNING, /*!< Indicates sender require signed messages (bool param) */
167+
I_NONCE_REQUEST, /*!< Request for a nonce */
168+
I_NONCE_RESPONSE, /*!< Payload is nonce data */
169169
I_HEARTBEAT, I_PRESENTATION, I_DISCOVER, I_DISCOVER_RESPONSE, I_HEARTBEAT_RESPONSE
170170
} mysensor_internal;
171171

libraries/MySensors/core/MySensorCore.cpp

+2-10
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,12 @@ static inline bool isValidParent( const uint8_t parent ) {
6262
void _begin() {
6363
#if !defined(MY_DISABLED_SERIAL)
6464
hwInit();
65-
#endif
66-
67-
#if defined(MY_SIGNING_SOFT)
68-
// initialize pseudo-RNG
69-
randomSeed(analogRead(MY_SIGNING_SOFT_RANDOMSEED_PIN));
7065
#endif
7166

7267
debug(PSTR("Starting " MY_NODE_TYPE " (" MY_CAPABILIIES ", " LIBRARY_VERSION ")\n"));
7368

69+
signerInit();
70+
7471
#if defined(MY_RADIO_FEATURE)
7572
_failedTransmissions = 0;
7673

@@ -107,11 +104,6 @@ void _begin() {
107104
#endif
108105

109106

110-
#if defined(MY_SIGNING_FEATURE)
111-
// Read out the signing requirements from EEPROM
112-
hwReadConfigBlock((void*)_doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS, sizeof(_doSign));
113-
#endif
114-
115107
#if defined(MY_LEDS_BLINKING_FEATURE)
116108
ledsInit();
117109
#endif

libraries/MySensors/core/MySigning.cpp

+285
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,292 @@
1919

2020
#include "MySigning.h"
2121

22+
#if defined(MY_DEBUG_VERBOSE)
23+
#define SIGN_DEBUG(x,...) debug(x, ##__VA_ARGS__)
24+
#else
25+
#define SIGN_DEBUG(x,...)
26+
#endif
2227

28+
#if defined(MY_SIGNING_REQUEST_SIGNATURES) && (!defined(MY_SIGNING_ATSHA204) && !defined(MY_SIGNING_SOFT))
29+
#error You have to pick either MY_SIGNING_ATSHA204 or MY_SIGNING_SOFT in order to require signatures!
30+
#endif
31+
#ifdef MY_SIGNING_FEATURE
2332
uint8_t _doSign[32]; // Bitfield indicating which sensors require signed communication
2433
MyMessage _msgSign; // Buffer for message to sign.
34+
uint8_t _signingNonceStatus;
2535

36+
// Status when waiting for signing nonce in signerProcessInternal
37+
enum { SIGN_WAITING_FOR_NONCE = 0, SIGN_OK = 1 };
38+
39+
// Macros for manipulating signing requirement table
40+
#define DO_SIGN(node) (~_doSign[node>>3]&(1<<node%8))
41+
#define SET_SIGN(node) (_doSign[node>>3]&=~(1<<node%8))
42+
#define CLEAR_SIGN(node) (_doSign[node>>3]|=(1<<node%8))
43+
44+
/*
45+
* Stores signing identifier and a new nonce in provided message for signing operations.
46+
*
47+
* All space in message payload buffer is used for signing identifier and nonce.
48+
* Returns false if subsystem is busy processing an ongoing signing operation.
49+
* If successful, this marks the start of a signing operation at the receiving side so
50+
* implementation is expected to do any necessary initializations within this call.
51+
* This function is typically called as action when receiving a I_NONCE_REQUEST message.
52+
*/
53+
#if defined(MY_SIGNING_SOFT)
54+
extern bool signerAtsha204SoftCheckTimer(void);
55+
extern bool signerAtsha204SoftGetNonce(MyMessage &msg);
56+
extern bool signerAtsha204SoftPutNonce(MyMessage &msg);
57+
extern bool signerAtsha204SoftVerifyMsg(MyMessage &msg);
58+
extern bool signerAtsha204SoftSignMsg(MyMessage &msg);
59+
#endif
60+
#if defined(MY_SIGNING_ATSHA204)
61+
extern bool signerAtsha204CheckTimer(void);
62+
extern bool signerAtsha204GetNonce(MyMessage &msg);
63+
extern bool signerAtsha204PutNonce(MyMessage &msg);
64+
extern bool signerAtsha204VerifyMsg(MyMessage &msg);
65+
extern bool signerAtsha204SignMsg(MyMessage &msg);
66+
#endif
67+
68+
// Helper function to centralize signing/verification exceptions
69+
static bool skipSign(MyMessage &msg) {
70+
if mGetAck(msg) {
71+
SIGN_DEBUG(PSTR("Skipping security for ACK on command %d type %d\n"), mGetCommand(msg), msg.type);
72+
return true;
73+
} else if (mGetCommand(msg) == C_INTERNAL &&
74+
(msg.type == I_NONCE_REQUEST || msg.type == I_NONCE_RESPONSE || msg.type == I_REQUEST_SIGNING ||
75+
msg.type == I_ID_REQUEST || msg.type == I_ID_RESPONSE ||
76+
msg.type == I_FIND_PARENT || msg.type == I_FIND_PARENT_RESPONSE ||
77+
msg.type == I_HEARTBEAT || msg.type == I_HEARTBEAT_RESPONSE)) {
78+
SIGN_DEBUG(PSTR("Skipping security for command %d type %d\n"), mGetCommand(msg), msg.type);
79+
return true;
80+
} else if (mGetCommand(msg) == C_STREAM &&
81+
(msg.type == ST_FIRMWARE_REQUEST || msg.type == ST_FIRMWARE_RESPONSE ||
82+
msg.type == ST_SOUND || msg.type == ST_IMAGE)) {
83+
SIGN_DEBUG(PSTR("Skipping security for command %d type %d\n"), mGetCommand(msg), msg.type);
84+
return true;
85+
} else {
86+
return false;
87+
}
88+
}
89+
#endif // MY_SIGNING_FEATURE
90+
91+
void signerInit(void) {
92+
#if defined(MY_SIGNING_FEATURE)
93+
// Read out the signing requirements from EEPROM
94+
hwReadConfigBlock((void*)_doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS, sizeof(_doSign));
95+
96+
#if defined(MY_SIGNING_SOFT)
97+
// initialize pseudo-RNG
98+
randomSeed(analogRead(MY_SIGNING_SOFT_RANDOMSEED_PIN));
99+
#endif
100+
#endif
101+
}
102+
103+
void signerPresentation(MyMessage &msg) {
104+
#ifdef MY_SIGNING_FEATURE
105+
#if defined(MY_SIGNING_REQUEST_SIGNATURES)
106+
_sendRoute(build(msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID,
107+
C_INTERNAL, I_REQUEST_SIGNING, false).set(true));
108+
// If we do require signing, wait for the gateway to tell us how it prefer us to transmit our messages
109+
SIGN_DEBUG(PSTR("Request for signing sent, waiting for GW response...\n"));
110+
wait(2000);
111+
#else
112+
_sendRoute(build(msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID,
113+
C_INTERNAL, I_REQUEST_SIGNING, false).set(false));
114+
#endif
115+
#else
116+
// Ww do not support signing, make sure gateway knows this
117+
_sendRoute(build(msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID,
118+
C_INTERNAL, I_REQUEST_SIGNING, false).set(false));
119+
#endif
120+
}
121+
122+
bool signerProcessInternal(MyMessage &msg) {
123+
if (mGetCommand(msg) == C_INTERNAL) {
124+
#if !defined(MY_SIGNING_FEATURE) && defined(MY_GATEWAY_FEATURE)
125+
// If we act as gateway and do not have the signing feature and receive a signing request we still
126+
// need to do make sure the requester does not believe the gateway still require signatures
127+
if (msg.type == I_REQUEST_SIGNING) {
128+
_sendRoute(build(msg, _nc.nodeId, msg.sender, NODE_SENSOR_ID,
129+
C_INTERNAL, I_REQUEST_SIGNING, false).set((uint8_t)false));
130+
SIGN_DEBUG(PSTR("Informing node %d that we do not require signatures because we do not support it\n"),
131+
msg.sender);
132+
return true; // No need to further process I_REQUEST_SIGNING in this case
133+
}
134+
#elif defined(MY_SIGNING_FEATURE)
135+
if (msg.type == I_NONCE_REQUEST) {
136+
#if defined(MY_SIGNING_SOFT)
137+
if (signerAtsha204SoftGetNonce(msg)) {
138+
#endif
139+
#if defined(MY_SIGNING_ATSHA204)
140+
if (signerAtsha204GetNonce(msg)) {
141+
#endif
142+
SIGN_DEBUG(PSTR("Transmittng nonce\n"));
143+
_sendRoute(build(msg, _nc.nodeId, msg.sender, NODE_SENSOR_ID,
144+
C_INTERNAL, I_NONCE_RESPONSE, false));
145+
} else {
146+
SIGN_DEBUG(PSTR("Failed to generate nonce!\n"));
147+
}
148+
return true; // No need to further process I_NONCE_REQUEST
149+
} else if (msg.type == I_REQUEST_SIGNING) {
150+
if (msg.getBool()) {
151+
// We received an indicator that the sender require us to sign all messages we send to it
152+
SIGN_DEBUG(PSTR("Mark node %d as one that require signed messages\n"), msg.sender);
153+
SET_SIGN(msg.sender);
154+
} else {
155+
// We received an indicator that the sender does not require us to sign all messages we send to it
156+
SIGN_DEBUG(PSTR("Mark node %d as one that do not require signed messages\n"), msg.sender);
157+
CLEAR_SIGN(msg.sender);
158+
}
159+
// Save updated table
160+
hwWriteConfigBlock((void*)_doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS, sizeof(_doSign));
161+
162+
// Inform sender about our preference if we are a gateway, but only require signing if the sender
163+
// required signing
164+
// We do not want a gateway to require signing from all nodes in a network just because it wants one node
165+
// to sign it's messages
166+
#if defined (MY_GATEWAY_FEATURE)
167+
#ifdef MY_SIGNING_REQUEST_SIGNATURES
168+
if (DO_SIGN(msg.sender)) {
169+
_sendRoute(build(msg, _nc.nodeId, msg.sender, NODE_SENSOR_ID,
170+
C_INTERNAL, I_REQUEST_SIGNING, false).set((uint8_t)true));
171+
SIGN_DEBUG(PSTR("Informing node %d that we require signatures\n"), msg.sender);
172+
}
173+
else {
174+
_sendRoute(build(msg, _nc.nodeId, msg.sender, NODE_SENSOR_ID,
175+
C_INTERNAL, I_REQUEST_SIGNING, false).set((uint8_t)false));
176+
SIGN_DEBUG(PSTR("Informing node %d that we do not require signatures\n"), msg.sender);
177+
}
178+
#else // no MY_SIGNING_REQUEST_SIGNATURES
179+
_sendRoute(build(msg, _nc.nodeId, msg.sender, NODE_SENSOR_ID,
180+
C_INTERNAL, I_REQUEST_SIGNING, false).set((uint8_t)false));
181+
SIGN_DEBUG(PSTR("Informing node %d that we do not require signatures\n"), msg.sender);
182+
#endif
183+
#endif // MY_GATEWAY_FEATURE
184+
return true; // No need to further process I_REQUEST_SIGNING
185+
} else if (msg.type == I_NONCE_RESPONSE) {
186+
// Proceed with signing if nonce has been received
187+
SIGN_DEBUG(PSTR("Nonce received from %d. Proceeding with signing...\n"), msg.sender);
188+
if (msg.sender != _msgSign.destination) {
189+
SIGN_DEBUG(PSTR("Nonce did not come from the destination (%d) of the message to be signed! "
190+
"It came from %d.\n"), _msgSign.destination, msg.sender);
191+
SIGN_DEBUG(PSTR("Silently discarding this nonce\n"));
192+
return true; // No need to further process I_NONCE_RESPONSE
193+
}
194+
#if defined(MY_SIGNING_SOFT)
195+
if (!signerAtsha204SoftPutNonce(msg)) {
196+
#endif
197+
#if defined(MY_SIGNING_ATSHA204)
198+
if (!signerAtsha204PutNonce(msg)) {
199+
#endif
200+
SIGN_DEBUG(PSTR("Failed to store nonce to signing backend!\n"));
201+
#if defined(MY_SIGNING_SOFT)
202+
} else if (!signerAtsha204SoftSignMsg(_msgSign)) {
203+
#endif
204+
#if defined(MY_SIGNING_ATSHA204)
205+
} else if (!signerAtsha204SignMsg(_msgSign)) {
206+
#endif
207+
SIGN_DEBUG(PSTR("Failed to sign message!\n"));
208+
} else {
209+
SIGN_DEBUG(PSTR("Message signed\n"));
210+
_signingNonceStatus = SIGN_OK; // _msgSign now contains the signed message pending transmission
211+
}
212+
return true; // No need to further process I_NONCE_RESPONSE
213+
}
214+
#endif // MY_SIGNING_FEATURE
215+
}
216+
return false;
217+
}
218+
219+
bool signerCheckTimer(void) {
220+
#if defined(MY_SIGNING_SOFT)
221+
return signerAtsha204SoftCheckTimer();
222+
#elif defined(MY_SIGNING_ATSHA204)
223+
return signerAtsha204CheckTimer();
224+
#else
225+
return true; // Without a configured backend, we always give "positive" results
226+
#endif
227+
}
228+
229+
bool signerSignMsg(MyMessage &msg) {
230+
#if defined(MY_SIGNING_FEATURE)
231+
// If destination is known to require signed messages and we are the sender,
232+
// sign this message unless it is a handshake message
233+
if (DO_SIGN(msg.destination) && msg.sender == _nc.nodeId) {
234+
if (skipSign(msg)) {
235+
return true;
236+
} else {
237+
// Send nonce-request
238+
_signingNonceStatus=SIGN_WAITING_FOR_NONCE;
239+
if (!_sendRoute(build(_msgTmp, _nc.nodeId, msg.destination, msg.sensor,
240+
C_INTERNAL, I_NONCE_REQUEST, false).set(""))) {
241+
SIGN_DEBUG(PSTR("Failed to transmit nonce request!\n"));
242+
return false;
243+
}
244+
SIGN_DEBUG(PSTR("Nonce requested from %d. Waiting...\n"), msg.destination);
245+
// We have to wait for the nonce to arrive before we can sign our original message
246+
// Other messages could come in-between. We trust _process() takes care of them
247+
unsigned long enter = hwMillis();
248+
_msgSign = msg; // Copy the message to sign since message buffer might be touched in _process()
249+
while (hwMillis() - enter < MY_VERIFICATION_TIMEOUT_MS && _signingNonceStatus==SIGN_WAITING_FOR_NONCE) {
250+
_process();
251+
}
252+
if (hwMillis() - enter > MY_VERIFICATION_TIMEOUT_MS) {
253+
SIGN_DEBUG(PSTR("Timeout waiting for nonce!\n"));
254+
return false;
255+
}
256+
if (_signingNonceStatus == SIGN_OK) {
257+
// process() received a nonce and signerProcessInternal successfully signed the message
258+
msg = _msgSign; // Write the signed message back
259+
SIGN_DEBUG(PSTR("Message to send has been signed\n"));
260+
} else {
261+
SIGN_DEBUG(PSTR("Message to send could not be signed!\n"));
262+
return false;
263+
}
264+
// After this point, only the 'last' member of the message structure is allowed to be altered if the
265+
// message has been signed, or signature will become invalid and the message rejected by the receiver
266+
}
267+
} else if (_nc.nodeId == msg.sender) {
268+
mSetSigned(msg, 0); // Message is not supposed to be signed, make sure it is marked unsigned
269+
}
270+
#else
271+
(void)msg;
272+
#endif // MY_SIGNING_FEATURE
273+
return true;
274+
}
275+
276+
bool signerVerifyMsg(MyMessage &msg) {
277+
bool verificationResult = true;
278+
// Before processing message, reject unsigned messages if signing is required and check signature
279+
// (if it is signed and addressed to us)
280+
// Note that we do not care at all about any signature found if we do not require signing
281+
#if defined(MY_SIGNING_FEATURE) && defined(MY_SIGNING_REQUEST_SIGNATURES)
282+
// If we are a node, or we are a gateway and the sender require signatures
283+
// and we are the destination...
284+
if ((!MY_IS_GATEWAY || DO_SIGN(msg.sender)) && msg.destination == _nc.nodeId) {
285+
// Internal messages of certain types are not verified
286+
if (skipSign(msg)) {
287+
verificationResult = true;
288+
}
289+
else if (!mGetSigned(msg)) {
290+
// Got unsigned message that should have been signed
291+
SIGN_DEBUG(PSTR("Message is not signed, but it should have been!\n"));
292+
verificationResult = false;
293+
} else {
294+
#if defined(MY_SIGNING_SOFT)
295+
verificationResult = signerAtsha204SoftVerifyMsg(msg);
296+
#endif
297+
#if defined(MY_SIGNING_ATSHA204)
298+
verificationResult = signerAtsha204VerifyMsg(msg);
299+
#endif
300+
if (!verificationResult) {
301+
SIGN_DEBUG(PSTR("Signature verification failed!\n"));
302+
}
303+
mSetSigned(msg,0); // Clear the sign-flag now as verification is completed
304+
}
305+
}
306+
#else
307+
(void)msg;
308+
#endif // MY_SIGNING_REQUEST_SIGNATURES
309+
return verificationResult;
310+
}

0 commit comments

Comments
 (0)