diff --git a/libraries/MySensors/MyConfig.h b/libraries/MySensors/MyConfig.h index 6c0b00f..ffe3978 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -546,7 +546,64 @@ // If MY_CONTROLLER_IP_ADDRESS is left un-defined, gateway acts as server allowing incoming connections. //#define MY_CONTROLLER_IP_ADDRESS 192, 168, 178, 254 +/** + * @defgroup MyLockgrp MyNodeLock + * @ingroup internals + * @{ + * @brief The node lock feature is a security related feature. It locks a node that suspect itself for being + * under some form of attack. + * + * This is achieved by having a counter stored in EEPROM which decrements when suspicious activity is detected. + * If the counter reaches 0, node will not work anymore and will transmit a @ref I_LOCKED message to the + * gateway/controller with 30m intervals. Payload is a string with a reason for the locking. + * The string is abbreviated to accomodate a signature. The following abbreviations exist at the moment: + * - LDB (Locked During Boot) + * - TMNR (Too Many Nonce Requests) + * - TMFV (Too Many Failed Verifications) + * + * Typically, the counter only decrements when suspicious activity happens in a row. + * It is reset if legit traffic is present. + + * Examples of malicious activity are: + * - Repeatedly incorrectly checksummed OTA firmware + * - Repeated requests for signing nonces without properly signed messages arriving + * - Repeatedly failed signature verifications + * + * If counter reaches zero, node locks down and EEPROM has to be erased/reset to reactivate node. + * Node can also be unlocked by grounding a pin (see @ref MY_NODE_UNLOCK_PIN). + * + * The size of the counter can be adjusted using @ref MY_NODE_LOCK_COUNTER_MAX. + * + * @def MY_NODE_LOCK_FEATURE + * @brief Enable this to activate intrusion prevention mechanisms on the node. + */ +//#define MY_NODE_LOCK_FEATURE + +/** + * @def MY_NODE_UNLOCK_PIN + * @brief By grounding this pin durig reset of a locked node, the node will unlock. + * + * If using a secure bootloader, grounding the pin is the only option to reactivate the node. + * If using stock Android bootloader or a DualOptiBoot it is also possible to download a sketch + * using serial protocol to erase EEPROM to unlock the node. + */ +#ifndef MY_NODE_UNLOCK_PIN +#define MY_NODE_UNLOCK_PIN 14 #endif + +/** + * @def MY_NODE_LOCK_COUNTER_MAX + * @brief Maximum accepted occurances of suspected malicious activity in a node. + * + * Counter decrements on reoccuring incidents but resets if legitimate behaviour is identified. + */ +#ifndef MY_NODE_LOCK_COUNTER_MAX +#define MY_NODE_LOCK_COUNTER_MAX 5 +#endif +/** @}*/ // Node lock group + +#endif + // Doxygen specific constructs, not included when built normally // This is used to enable disabled macros/definitions to be included in the documentation as well. #if DOXYGEN diff --git a/libraries/MySensors/core/MyEepromAddresses.h b/libraries/MySensors/core/MyEepromAddresses.h index 510126f..3c94be3 100644 --- a/libraries/MySensors/core/MyEepromAddresses.h +++ b/libraries/MySensors/core/MyEepromAddresses.h @@ -38,6 +38,7 @@ #define EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS (EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS+32) // This is set with SecurityPersonalizer.ino #define EEPROM_SIGNING_SOFT_SERIAL_ADDRESS (EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS+32) // This is set with SecurityPersonalizer.ino #define EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS (EEPROM_SIGNING_SOFT_SERIAL_ADDRESS+9) // This is set with SecurityPersonalizer.ino -#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS+16) // First free address for sketch static configuration +#define EEPROM_NODE_LOCK_COUNTER (EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS+16) +#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_NODE_LOCK_COUNTER+1) // First free address for sketch static configuration #endif diff --git a/libraries/MySensors/core/MyMessage.h b/libraries/MySensors/core/MyMessage.h index 8a68c56..d828147 100644 --- a/libraries/MySensors/core/MyMessage.h +++ b/libraries/MySensors/core/MyMessage.h @@ -170,7 +170,8 @@ typedef enum { I_SIGNING_PRESENTATION, //!< Provides signing related preferences (first byte is preference version) I_NONCE_REQUEST, //!< Request for a nonce I_NONCE_RESPONSE, //!< Payload is nonce data - I_HEARTBEAT, I_PRESENTATION, I_DISCOVER, I_DISCOVER_RESPONSE, I_HEARTBEAT_RESPONSE + I_HEARTBEAT, I_PRESENTATION, I_DISCOVER, I_DISCOVER_RESPONSE, I_HEARTBEAT_RESPONSE, + I_LOCKED //!< Node is locked (reason in string-payload) } mysensor_internal; diff --git a/libraries/MySensors/core/MySensorCore.cpp b/libraries/MySensors/core/MySensorCore.cpp index feabd96..cc7745b 100644 --- a/libraries/MySensors/core/MySensorCore.cpp +++ b/libraries/MySensors/core/MySensorCore.cpp @@ -152,6 +152,31 @@ void _begin() { } #endif +#ifdef MY_NODE_LOCK_FEATURE + // Check if node has been locked down + if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER) == 0) { + // Node is locked, check if unlock pin is asserted, else hang the node + pinMode(MY_NODE_UNLOCK_PIN, INPUT_PULLUP); + // Make a short delay so we are sure any large external nets are fully pulled + unsigned long enter = hwMillis(); + while (hwMillis() - enter < 2); + if (digitalRead(MY_NODE_UNLOCK_PIN) == 0) { + // Pin is grounded, reset lock counter + hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, MY_NODE_LOCK_COUNTER_MAX); + // Disable pullup + pinMode(MY_NODE_UNLOCK_PIN, INPUT); + debug(PSTR("Node is unlocked.\n")); + } else { + // Disable pullup + pinMode(MY_NODE_UNLOCK_PIN, INPUT); + nodeLock("LDB"); //Locked during boot + } + } else if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER) == 0xFF) { + // Reset walue + hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, MY_NODE_LOCK_COUNTER_MAX); + } +#endif + // Call sketch setup if (setup) setup(); @@ -162,6 +187,7 @@ void _begin() { #endif if (presentation) presentation(); + debug(PSTR("Init complete, id=%d, parent=%d, distance=%d\n"), _nc.nodeId, _nc.parentNodeId, _nc.distance); } @@ -400,4 +426,22 @@ int8_t smartSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t return ret; } +#ifdef MY_NODE_LOCK_FEATURE +void nodeLock(const char* str) { + // Make sure EEPROM is updated to locked status + hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, 0); + while (1) { + debug(PSTR("Node is locked. Ground pin %d and reset to unlock.\n"), MY_NODE_UNLOCK_PIN); + #if defined(MY_GATEWAY_ESP8266) + yield(); + #endif + _sendRoute(build(_msg, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, + C_INTERNAL, I_LOCKED, false).set(str)); + #if defined(MY_RADIO_FEATURE) + transportPowerDown(); + #endif + (void)hwSleep((unsigned long)1000*60*30); // Sleep for 30 min before resending LOCKED message + } +} +#endif diff --git a/libraries/MySensors/core/MySensorCore.h b/libraries/MySensors/core/MySensorCore.h index b7b25f0..2242e40 100644 --- a/libraries/MySensors/core/MySensorCore.h +++ b/libraries/MySensors/core/MySensorCore.h @@ -202,6 +202,20 @@ int8_t smartSleep(uint8_t interrupt, uint8_t mode, unsigned long ms=0); int8_t sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms=0); int8_t smartSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms=0); +#ifdef MY_NODE_LOCK_FEATURE +/** + * @ingroup MyLockgrp + * @ingroup internals + * @brief Lock a node and transmit provided message with 30m intervals + * + * This function is called if suspicious activity has exceeded the threshold (see + * @ref ATTACK_COUNTER_MAX). Unlocking with a normal Arduino bootloader require erasing the EEPROM + * while unlocking with a custom bootloader require holding @ref UNLOCK_PIN low during power on/reset. + * + * @param str The string to transmit. + */ +void nodeLock(const char* str); +#endif /****** PRIVATE ********/ diff --git a/libraries/MySensors/core/MySigning.cpp b/libraries/MySensors/core/MySigning.cpp index 1cb0578..09de114 100644 --- a/libraries/MySensors/core/MySigning.cpp +++ b/libraries/MySensors/core/MySigning.cpp @@ -39,6 +39,11 @@ uint8_t _doWhitelist[32]; // Bitfield indicating which sensors require serial sa MyMessage _msgSign; // Buffer for message to sign. uint8_t _signingNonceStatus; +#ifdef MY_NODE_LOCK_FEATURE +static uint8_t nof_nonce_requests = 0; +static uint8_t nof_failed_verifications = 0; +#endif + // Status when waiting for signing nonce in signerProcessInternal enum { SIGN_WAITING_FOR_NONCE = 0, SIGN_OK = 1 }; @@ -161,6 +166,13 @@ bool signerProcessInternal(MyMessage &msg) { } #elif defined(MY_SIGNING_FEATURE) if (msg.type == I_NONCE_REQUEST) { +#if defined(MY_NODE_LOCK_FEATURE) + nof_nonce_requests++; + SIGN_DEBUG(PSTR("Nonce requests left until lockdown: %d\n"), MY_NODE_LOCK_COUNTER_MAX-nof_nonce_requests); + if (nof_nonce_requests >= MY_NODE_LOCK_COUNTER_MAX) { + nodeLock("TMNR"); //Too many nonces requested + } +#endif #if defined(MY_SIGNING_SOFT) if (signerAtsha204SoftGetNonce(msg)) { #endif @@ -353,6 +365,19 @@ bool signerVerifyMsg(MyMessage &msg) { if (!verificationResult) { SIGN_DEBUG(PSTR("Signature verification failed!\n")); } +#if defined(MY_NODE_LOCK_FEATURE) + if (verificationResult) { + // On successful verification, clear lock counters + nof_nonce_requests = 0; + nof_failed_verifications = 0; + } else { + nof_failed_verifications++; + SIGN_DEBUG(PSTR("Failed verification attempts left until lockdown: %d\n"), MY_NODE_LOCK_COUNTER_MAX-nof_failed_verifications); + if (nof_failed_verifications >= MY_NODE_LOCK_COUNTER_MAX) { + nodeLock("TMFV"); //Too many failed verifications + } + } +#endif mSetSigned(msg,0); // Clear the sign-flag now as verification is completed } } diff --git a/libraries/MySensors/examples/SecureActuator/SecureActuator.ino b/libraries/MySensors/examples/SecureActuator/SecureActuator.ino index 308b8e9..655055f 100644 --- a/libraries/MySensors/examples/SecureActuator/SecureActuator.ino +++ b/libraries/MySensors/examples/SecureActuator/SecureActuator.ino @@ -43,6 +43,7 @@ #define MY_DEBUG //!< Enable debug prints to serial monitor #define MY_DEBUG_VERBOSE_SIGNING //!< Enable signing related debug prints to serial monitor +#define MY_NODE_LOCK_FEATURE //!< Enable lockdown of node if suspicious activity is detected // Enable and select radio type attached #define MY_RADIO_NRF24 //!< NRF24L01 radio driver diff --git a/libraries/MySensors/keywords.txt b/libraries/MySensors/keywords.txt index fc148be..0a18e95 100644 --- a/libraries/MySensors/keywords.txt +++ b/libraries/MySensors/keywords.txt @@ -109,4 +109,7 @@ MY_GATEWAY_MQTT_CLIENT LITERAL1 MY_DISABLE_REMOTE_RESET LITERAL1 MY_RS485_DE_PIN LITERAL1 MY_SIGNING_REQUEST_SIGNATURES LITERAL1 -MY_SMART_SLEEP_WAIT_DURATION LITERAL1 \ No newline at end of file +MY_SMART_SLEEP_WAIT_DURATION LITERAL1 +MY_NODE_LOCK_FEATURE LITERAL1 +MY_NODE_UNLOCK_PIN LITERAL1 +MY_NODE_LOCK_COUNTER_MAX LITERAL1 \ No newline at end of file diff --git a/libraries/MySensors/tests/Arduino/sketches/hard_signing_whitelisting_full_debug_nodelock/hard_signing_whitelisting_full_debug_nodelock.ino b/libraries/MySensors/tests/Arduino/sketches/hard_signing_whitelisting_full_debug_nodelock/hard_signing_whitelisting_full_debug_nodelock.ino new file mode 100644 index 0000000..78daf06 --- /dev/null +++ b/libraries/MySensors/tests/Arduino/sketches/hard_signing_whitelisting_full_debug_nodelock/hard_signing_whitelisting_full_debug_nodelock.ino @@ -0,0 +1,33 @@ +/* + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2015 Sensnology AB + * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + ******************************* + */ +#define MY_DEBUG +#define MY_DEBUG_VERBOSE_SIGNING +#define MY_RADIO_NRF24 +//#define MY_SIGNING_SOFT +#define MY_SIGNING_ATSHA204 +#define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}}} +#define MY_SIGNING_REQUEST_SIGNATURES +#define MY_SIGNING_SOFT_RANDOMSEED_PIN 7 +#define MY_SIGNING_ATSHA204_PIN 17 +#define MY_NODE_LOCK_FEATURE + +#include +#include