Skip to content

Commit 7de2764

Browse files
committed
Node lock feature
When activated, suspicious signing related activities will eventually cause the node to lock down and transmit a I_LOCKED message to the gateway at 30m intervals. Unlocking is done by grounding a pin or erasing the EEPROM. See Doxygen for details on use. Fixes arduino#280
1 parent 4a18797 commit 7de2764

File tree

9 files changed

+182
-3
lines changed

9 files changed

+182
-3
lines changed

libraries/MySensors/MyConfig.h

+57
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,64 @@
546546
// If MY_CONTROLLER_IP_ADDRESS is left un-defined, gateway acts as server allowing incoming connections.
547547
//#define MY_CONTROLLER_IP_ADDRESS 192, 168, 178, 254
548548

549+
/**
550+
* @defgroup MyLockgrp MyNodeLock
551+
* @ingroup internals
552+
* @{
553+
* @brief The node lock feature is a security related feature. It locks a node that suspect itself for being
554+
* under some form of attack.
555+
*
556+
* This is achieved by having a counter stored in EEPROM which decrements when suspicious activity is detected.
557+
* If the counter reaches 0, node will not work anymore and will transmit a @ref I_LOCKED message to the
558+
* gateway/controller with 30m intervals. Payload is a string with a reason for the locking.
559+
* The string is abbreviated to accomodate a signature. The following abbreviations exist at the moment:
560+
* - LDB (Locked During Boot)
561+
* - TMNR (Too Many Nonce Requests)
562+
* - TMFV (Too Many Failed Verifications)
563+
*
564+
* Typically, the counter only decrements when suspicious activity happens in a row.
565+
* It is reset if legit traffic is present.
566+
567+
* Examples of malicious activity are:
568+
* - Repeatedly incorrectly checksummed OTA firmware
569+
* - Repeated requests for signing nonces without properly signed messages arriving
570+
* - Repeatedly failed signature verifications
571+
*
572+
* If counter reaches zero, node locks down and EEPROM has to be erased/reset to reactivate node.
573+
* Node can also be unlocked by grounding a pin (see @ref MY_NODE_UNLOCK_PIN).
574+
*
575+
* The size of the counter can be adjusted using @ref MY_NODE_LOCK_COUNTER_MAX.
576+
*
577+
* @def MY_NODE_LOCK_FEATURE
578+
* @brief Enable this to activate intrusion prevention mechanisms on the node.
579+
*/
580+
//#define MY_NODE_LOCK_FEATURE
581+
582+
/**
583+
* @def MY_NODE_UNLOCK_PIN
584+
* @brief By grounding this pin durig reset of a locked node, the node will unlock.
585+
*
586+
* If using a secure bootloader, grounding the pin is the only option to reactivate the node.
587+
* If using stock Android bootloader or a DualOptiBoot it is also possible to download a sketch
588+
* using serial protocol to erase EEPROM to unlock the node.
589+
*/
590+
#ifndef MY_NODE_UNLOCK_PIN
591+
#define MY_NODE_UNLOCK_PIN 14
592+
#endif
593+
594+
/**
595+
* @def MY_NODE_LOCK_COUNTER_MAX
596+
* @brief Maximum accepted occurances of suspected malicious activity in a node.
597+
*
598+
* Counter decrements on reoccuring incidents but resets if legitimate behaviour is identified.
599+
*/
600+
#ifndef MY_NODE_LOCK_COUNTER_MAX
601+
#define MY_NODE_LOCK_COUNTER_MAX 5
549602
#endif
603+
/** @}*/ // Node lock group
604+
605+
#endif
606+
550607
// Doxygen specific constructs, not included when built normally
551608
// This is used to enable disabled macros/definitions to be included in the documentation as well.
552609
#if DOXYGEN

libraries/MySensors/core/MyEepromAddresses.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#define EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS (EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS+32) // This is set with SecurityPersonalizer.ino
3939
#define EEPROM_SIGNING_SOFT_SERIAL_ADDRESS (EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS+32) // This is set with SecurityPersonalizer.ino
4040
#define EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS (EEPROM_SIGNING_SOFT_SERIAL_ADDRESS+9) // This is set with SecurityPersonalizer.ino
41-
#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS+16) // First free address for sketch static configuration
41+
#define EEPROM_NODE_LOCK_COUNTER (EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS+16)
42+
#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_NODE_LOCK_COUNTER+1) // First free address for sketch static configuration
4243

4344
#endif

libraries/MySensors/core/MyMessage.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@ typedef enum {
170170
I_SIGNING_PRESENTATION, //!< Provides signing related preferences (first byte is preference version)
171171
I_NONCE_REQUEST, //!< Request for a nonce
172172
I_NONCE_RESPONSE, //!< Payload is nonce data
173-
I_HEARTBEAT, I_PRESENTATION, I_DISCOVER, I_DISCOVER_RESPONSE, I_HEARTBEAT_RESPONSE
173+
I_HEARTBEAT, I_PRESENTATION, I_DISCOVER, I_DISCOVER_RESPONSE, I_HEARTBEAT_RESPONSE,
174+
I_LOCKED //!< Node is locked (reason in string-payload)
174175
} mysensor_internal;
175176

176177

libraries/MySensors/core/MySensorCore.cpp

+44
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,31 @@ void _begin() {
152152
}
153153
#endif
154154

155+
#ifdef MY_NODE_LOCK_FEATURE
156+
// Check if node has been locked down
157+
if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER) == 0) {
158+
// Node is locked, check if unlock pin is asserted, else hang the node
159+
pinMode(MY_NODE_UNLOCK_PIN, INPUT_PULLUP);
160+
// Make a short delay so we are sure any large external nets are fully pulled
161+
unsigned long enter = hwMillis();
162+
while (hwMillis() - enter < 2);
163+
if (digitalRead(MY_NODE_UNLOCK_PIN) == 0) {
164+
// Pin is grounded, reset lock counter
165+
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, MY_NODE_LOCK_COUNTER_MAX);
166+
// Disable pullup
167+
pinMode(MY_NODE_UNLOCK_PIN, INPUT);
168+
debug(PSTR("Node is unlocked.\n"));
169+
} else {
170+
// Disable pullup
171+
pinMode(MY_NODE_UNLOCK_PIN, INPUT);
172+
nodeLock("LDB"); //Locked during boot
173+
}
174+
} else if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER) == 0xFF) {
175+
// Reset walue
176+
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, MY_NODE_LOCK_COUNTER_MAX);
177+
}
178+
#endif
179+
155180
// Call sketch setup
156181
if (setup)
157182
setup();
@@ -162,6 +187,7 @@ void _begin() {
162187
#endif
163188
if (presentation)
164189
presentation();
190+
165191
debug(PSTR("Init complete, id=%d, parent=%d, distance=%d\n"), _nc.nodeId, _nc.parentNodeId, _nc.distance);
166192
}
167193

@@ -400,4 +426,22 @@ int8_t smartSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t
400426
return ret;
401427
}
402428

429+
#ifdef MY_NODE_LOCK_FEATURE
430+
void nodeLock(const char* str) {
431+
// Make sure EEPROM is updated to locked status
432+
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, 0);
433+
while (1) {
434+
debug(PSTR("Node is locked. Ground pin %d and reset to unlock.\n"), MY_NODE_UNLOCK_PIN);
435+
#if defined(MY_GATEWAY_ESP8266)
436+
yield();
437+
#endif
438+
_sendRoute(build(_msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID,
439+
C_INTERNAL, I_LOCKED, false).set(str));
440+
#if defined(MY_RADIO_FEATURE)
441+
transportPowerDown();
442+
#endif
443+
(void)hwSleep((unsigned long)1000*60*30); // Sleep for 30 min before resending LOCKED message
444+
}
445+
}
446+
#endif
403447

libraries/MySensors/core/MySensorCore.h

+14
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,20 @@ int8_t smartSleep(uint8_t interrupt, uint8_t mode, unsigned long ms=0);
202202
int8_t sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms=0);
203203
int8_t smartSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms=0);
204204

205+
#ifdef MY_NODE_LOCK_FEATURE
206+
/**
207+
* @ingroup MyLockgrp
208+
* @ingroup internals
209+
* @brief Lock a node and transmit provided message with 30m intervals
210+
*
211+
* This function is called if suspicious activity has exceeded the threshold (see
212+
* @ref ATTACK_COUNTER_MAX). Unlocking with a normal Arduino bootloader require erasing the EEPROM
213+
* while unlocking with a custom bootloader require holding @ref UNLOCK_PIN low during power on/reset.
214+
*
215+
* @param str The string to transmit.
216+
*/
217+
void nodeLock(const char* str);
218+
#endif
205219

206220
/****** PRIVATE ********/
207221

libraries/MySensors/core/MySigning.cpp

+25
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ uint8_t _doWhitelist[32]; // Bitfield indicating which sensors require serial sa
3939
MyMessage _msgSign; // Buffer for message to sign.
4040
uint8_t _signingNonceStatus;
4141

42+
#ifdef MY_NODE_LOCK_FEATURE
43+
static uint8_t nof_nonce_requests = 0;
44+
static uint8_t nof_failed_verifications = 0;
45+
#endif
46+
4247
// Status when waiting for signing nonce in signerProcessInternal
4348
enum { SIGN_WAITING_FOR_NONCE = 0, SIGN_OK = 1 };
4449

@@ -161,6 +166,13 @@ bool signerProcessInternal(MyMessage &msg) {
161166
}
162167
#elif defined(MY_SIGNING_FEATURE)
163168
if (msg.type == I_NONCE_REQUEST) {
169+
#if defined(MY_NODE_LOCK_FEATURE)
170+
nof_nonce_requests++;
171+
SIGN_DEBUG(PSTR("Nonce requests left until lockdown: %d\n"), MY_NODE_LOCK_COUNTER_MAX-nof_nonce_requests);
172+
if (nof_nonce_requests >= MY_NODE_LOCK_COUNTER_MAX) {
173+
nodeLock("TMNR"); //Too many nonces requested
174+
}
175+
#endif
164176
#if defined(MY_SIGNING_SOFT)
165177
if (signerAtsha204SoftGetNonce(msg)) {
166178
#endif
@@ -353,6 +365,19 @@ bool signerVerifyMsg(MyMessage &msg) {
353365
if (!verificationResult) {
354366
SIGN_DEBUG(PSTR("Signature verification failed!\n"));
355367
}
368+
#if defined(MY_NODE_LOCK_FEATURE)
369+
if (verificationResult) {
370+
// On successful verification, clear lock counters
371+
nof_nonce_requests = 0;
372+
nof_failed_verifications = 0;
373+
} else {
374+
nof_failed_verifications++;
375+
SIGN_DEBUG(PSTR("Failed verification attempts left until lockdown: %d\n"), MY_NODE_LOCK_COUNTER_MAX-nof_failed_verifications);
376+
if (nof_failed_verifications >= MY_NODE_LOCK_COUNTER_MAX) {
377+
nodeLock("TMFV"); //Too many failed verifications
378+
}
379+
}
380+
#endif
356381
mSetSigned(msg,0); // Clear the sign-flag now as verification is completed
357382
}
358383
}

libraries/MySensors/examples/SecureActuator/SecureActuator.ino

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343

4444
#define MY_DEBUG //!< Enable debug prints to serial monitor
4545
#define MY_DEBUG_VERBOSE_SIGNING //!< Enable signing related debug prints to serial monitor
46+
#define MY_NODE_LOCK_FEATURE //!< Enable lockdown of node if suspicious activity is detected
4647

4748
// Enable and select radio type attached
4849
#define MY_RADIO_NRF24 //!< NRF24L01 radio driver

libraries/MySensors/keywords.txt

+4-1
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,7 @@ MY_GATEWAY_MQTT_CLIENT LITERAL1
109109
MY_DISABLE_REMOTE_RESET LITERAL1
110110
MY_RS485_DE_PIN LITERAL1
111111
MY_SIGNING_REQUEST_SIGNATURES LITERAL1
112-
MY_SMART_SLEEP_WAIT_DURATION LITERAL1
112+
MY_SMART_SLEEP_WAIT_DURATION LITERAL1
113+
MY_NODE_LOCK_FEATURE LITERAL1
114+
MY_NODE_UNLOCK_PIN LITERAL1
115+
MY_NODE_LOCK_COUNTER_MAX LITERAL1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* The MySensors Arduino library handles the wireless radio link and protocol
3+
* between your home built sensors/actuators and HA controller of choice.
4+
* The sensors forms a self healing radio network with optional repeaters. Each
5+
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
6+
* network topology allowing messages to be routed to nodes.
7+
*
8+
* Created by Henrik Ekblad <[email protected]>
9+
* Copyright (C) 2013-2015 Sensnology AB
10+
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
11+
*
12+
* Documentation: http://www.mysensors.org
13+
* Support Forum: http://forum.mysensors.org
14+
*
15+
* This program is free software; you can redistribute it and/or
16+
* modify it under the terms of the GNU General Public License
17+
* version 2 as published by the Free Software Foundation.
18+
*
19+
*******************************
20+
*/
21+
#define MY_DEBUG
22+
#define MY_DEBUG_VERBOSE_SIGNING
23+
#define MY_RADIO_NRF24
24+
//#define MY_SIGNING_SOFT
25+
#define MY_SIGNING_ATSHA204
26+
#define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}}}
27+
#define MY_SIGNING_REQUEST_SIGNATURES
28+
#define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
29+
#define MY_SIGNING_ATSHA204_PIN 17
30+
#define MY_NODE_LOCK_FEATURE
31+
32+
#include <SPI.h>
33+
#include <MySensor.h>

0 commit comments

Comments
 (0)