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:
Patrick Fallberg
2016-01-29 11:41:53 +01:00
parent 4a187974ad
commit 7de2764d2d
9 changed files with 182 additions and 3 deletions
+57
View File
@@ -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
+2 -1
View File
@@ -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
+2 -1
View File
@@ -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;
+44
View File
@@ -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
+14
View File
@@ -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 ********/
+25
View File
@@ -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
+4 -1
View File
@@ -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
@@ -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>