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 #280
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 ********/
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
MY_SMART_SLEEP_WAIT_DURATION LITERAL1
|
||||
MY_NODE_LOCK_FEATURE LITERAL1
|
||||
MY_NODE_UNLOCK_PIN LITERAL1
|
||||
MY_NODE_LOCK_COUNTER_MAX LITERAL1
|
||||
+33
@@ -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 <henrik.ekblad@mysensors.org>
|
||||
* 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 <SPI.h>
|
||||
#include <MySensor.h>
|
||||
Reference in New Issue
Block a user