Coredump and Duo Auth

This commit is contained in:
iranl
2025-01-21 21:41:43 +01:00
parent 6c3a0f078d
commit 2964f54e96
13 changed files with 356 additions and 52 deletions

1
DUOAUTH.md Normal file
View File

@@ -0,0 +1 @@
## Duo Push authentication

View File

@@ -2,6 +2,28 @@
The purpose of this mode is to have Nuki Hub work in conjunction with the official MQTT implementation by Nuki.
For Nuki Hub to work properly it is essential that Nuki Hub is notified by the lock of state changes (e.g. locking/unlocking) as soon as possible.
Starting from the first versions of Nuki Hub this was achieved by registering Nuki Hub as a Nuki Bridge.
When a Nuki Bridge is registered with a Nuki lock or opener the device will signal state changes using a Bluetooth Low Energy (BLE) iBeacon.
If this state change is seen by Nuki Hub the Nuki Hub device will connect to the lock/opener over BLE and receive additional information about the state change.
The beacon itself contains no information other than that a state change has occured.
After the Nuki Bridge device (e.g. Nuki Hub in this case) has connected and requested the state change report the Nuki device will reset the iBeacon state to the "unchanged" state.
With the introduction of WiFi/Thread and MQTT enabled locks there now was a second method of retrieving state changes, which we call "Hybrid mode".
In Hybrid mode Nuki Hub subscribes to the MQTT topics that the Nuki lock writes to itself using the official Nuki MQTT implementation over WiFi/Thread.
Nuki Hub will now pick up state changes from these official MQTT topics.
Part of these state changes is directly proxied to Nuki Hubs own MQTT topics (that your smart home system (e.g.) HomeAssistant is subscribed to) making this (in practise) just as fast as connecting your smart home application directly to the official MQTT implementation.
Nuki Hub will however also request additional information from the lock using BLE that is not available using the official Nuki MQTT implementation and publish this to Nuki Hubs MQTT topics.
If you have setup Hybrid mode consider taking a look at the information published on the official MQTT topics (base path "nuki/") and comparing this with the information Nuki Hub publishes (base topic "nukihub/" by default)
All functionality of Nuki Hub is available in both regular and hybrid mode.
In hybrid mode Nuki Hub will automatically choose the best way to communicate with the lock for retrieving information from the lock and pushing information (e.g. lock/unlock commands, change settings) to the lock based on the capabilities of the MQTT API and Bluetooth API.
When compared to regular/bridge mode this leads to speed increases in getting state changes and pushing state changes (because we can send and receive usefull information directly over MQTT without having to connect over BLE)
When compared to the official MQTT implementation this adds many many features that are not available in the official MQTT implementation and would normally require you to use the app or Web API (which has its own issues, downtime and cloud requirement).
**As the Nuki Smartlock Ultra has no support for the Nuki Bridge it is mandatory to setup Hybrid mode to receive prompt state changes from the lock in Nuki Hub.**
### Requirements ###
- ESP32 running Nuki Hub 9.08 or higher
@@ -40,10 +62,11 @@ The Hybrid Official MQTT over Thread + Nuki Hub solution allows for the best com
- Install Nuki Hub 9.08 or higher on a supported ESP32 device
- Make sure you are not paired as a bridge. Unpair your Nuki lock in Nuki Hub if Nuki Hub was paired as a bridge (this is mandatory even if you removed the bridge connection from the Nuki lock).
- Enable `Enable hybrid official MQTT and Nuki Hub setup`. The `Lock: Nuki Bridge is running alongside Nuki Hub (needs re-pairing if changed)` setting will be automatically be enabled.
- Enable `Enable hybrid official MQTT and Nuki Hub setup`. The `Lock: Nuki Bridge is running alongside Nuki Hub (needs re-pairing if changed)` setting will automatically be enabled.
- Optionally enable `Enable sending actions through official MQTT`, if not enabled lock actions will be sent over BLE as usual (slower)
- Set `Time between status updates when official MQTT is offline (seconds)` to a positive integer. If the Nuki lock MQTT connection goes offline for whatever reason Nuki Hub will update the lock state with the set interval in seconds.
<!--- Optionally enable `Retry command sent using official MQTT over BLE if failed`. If sending a lock action over the official MQTT implementation fails the command will be resent over BLE if this is enabled. Requires `Enable sending actions through official MQTT` to be enabled.!-->
- Optionally enable `Retry command sent using official MQTT over BLE if failed`. If sending a lock action over the official MQTT implementation fails the command will be resent over BLE if this is enabled. Requires `Enable sending actions through official MQTT` to be enabled.
- Optionally enable `Reboot Nuki lock on official MQTT failure`. If Nuki Hub determains that the official MQTT implementation is offline (usually because of the Nuki lock losing Thread/WiFi connection and not properly reconnecting) for more than 3 minutes Nuki Hub will try to reboot the Nuki lock (equivalent to removing and reinstalling the batteries).
- Save your configuration
- Consider setting the `Query intervals` on the `Advanced Nuki configuration` to high numbers (e.g. 86400) to further reduce battery drain.
- Pair your Nuki Lock with Nuki Hub

View File

@@ -171,10 +171,9 @@ Make sure "Bluetooth pairing" is enabled for the Nuki device by enabling this se
Before enabling pairing mode using the button on the Lock Ultra first setup Nuki Hub as follows:
- Enable both "Nuki Smartlock enabled" and "Nuki Smartlock Ultra enabled" settings on the "Basic Nuki Configuration" page and Save. Setting the "Nuki Smartlock Ultra enabled" will change multiple other NukiHub settings.
- Input your 6-digit Nuki Lock Ultra PIN on the "Credentials" page and Save
- Press the button on the Nuki device for a few seconds
- It is **strongly** recommended to setup and enable Hybrid mode over Thread/WiFi + official MQTT as Nuki Hub works best in Hybrid or Bridge mode and the Ultra does not support Bridge mode
- Press the button on the Nuki device for a few seconds until the LED ring lights up and remains lit.
- It is **strongly** recommended(/mandatory) to setup and enable Hybrid mode over Thread/WiFi + official MQTT as Nuki Hub works best in Hybrid or Bridge mode and the Ultra does not support Bridge mode
Pairing should be automatic if no lock is paired.<br>
When pairing is successful, the web interface should show "Paired: Yes".<br>
## Hybrid mode
@@ -318,13 +317,16 @@ In a browser navigate to the IP address assigned to the ESP32.
- User: Pick a username to enable HTTP authentication for the Web Configuration, Set to "#" to disable authentication.
- Password/Retype password: Pick a password to enable HTTP authentication for the Web Configuration.
- HTTP Authentication type: Select from Basic, Digest or Form based authentication. Digest authentication is more secure than Basic or Form based authentication, especially over unencrypted (HTTP) connections. Form based authentication works best with password managers. Note: Firefox seems to have issues with basic authentication.
- Bypass authentication for reverse proxy with IP: IP for which authentication is bypassed. Use in conjunction with a reverse proxy server with seperate authentication.
- Duo Push authentication enabled: Enable to use Duo Push Multi Factor Authentication (MFA)
- Bypass authentication for reverse proxy with IP: IP for which authentication is bypassed. Use in conjunction with a reverse proxy server with separate authentication.
- Duo Push authentication enabled: Enable to use Duo Push Multi Factor Authentication (MFA). See [Duo Push authentication](/DUOAUTH.md) for instructions on how to setup Duo Push authentication.
- Require Duo Push authentication for all sensitive Nuki Hub operations (changing/exporting settings): Enable to require Duo Push approval on all sensitive operations.
- Bypass Duo Push authentication by pressing the BOOT button during login: Enable to allow bypassing Duo Push authentication by pressing the BOOT button on the ESP during login. Note that this does not work on all ESP's (nor do all ESP's have a boot button to begin with). Test before relying on this function.
- Bypass Duo Push authentication by pulling GPIO High: Set to a GPIO pin to allow bypassing Duo Push authentication by pulling the GPIO high during login.
- Bypass Duo Push authentication by pulling GPIO Low: Set to a GPIO pin to allow bypassing Duo Push authentication by pulling the GPIO low during login.
- Duo API hostname: Set to the Duo API hostname
- Duo integration key: Set to the Duo integration key
- Duo secret key: Set to the Duo secret key
- Duo user: Set to the Duo user key
- Duo user: Set to the Duo user that you want to receive the push notification
- Session validity (in seconds): Session validity to use with form authentication when the "Remember me" checkbox is disabled, default 3600 seconds.
- Session validity remember (in hours): Session validity to use with form authentication when the "Remember me" checkbox is enabled, default 720 hours.
- Duo Session validity (in seconds): Session validity to use with Duo authentication when the "Remember me" checkbox is disabled, default 3600 seconds.
@@ -358,6 +360,11 @@ Create a (partial) backup of the current Nuki Hub settings by selecting any of t
Both of the above options will not backup pairing data, so you will have to manually pair Nuki devices when importing this export on a factory reset or new device.
- Export with redacted settings and pairing data: Will backup all settings and pairing data. Can be used to completely restore a factory reset or new device based on the settings of this device. (Re)pairing Nuki devices will not be needed when importing this export.
- Export HTTP SSL certificate and key (PSRAM devices only): Export your HTTP server SSL certificate and key
- Export MQTT SSL CA, client certificate and client key: Export your MQTT SSL CA and client certificate and client key
- Export Coredump: Export the complete log gathered from the last ESP crash
<br>
To import settings copy and paste the contents of the JSON file that is created by any of the above export options and select "Import".
After importing the device will reboot.

View File

@@ -83,7 +83,7 @@ void MqttLogger::sendBuffer()
{
doSerial = true;
}
if (doSerial)
if (doSerial && coredumpPrinted)
{
Serial.write(this->buffer, this->bufferCnt);
Serial.println();

View File

@@ -16,6 +16,8 @@
#define MQTT_MAX_PACKET_SIZE 1024
extern bool coredumpPrinted;
enum MqttLoggerMode {
MqttAndSerialFallback = 0,
SerialOnly = 1,

View File

@@ -122,3 +122,4 @@ CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
CONFIG_LWIP_DHCP_GET_NTP_SRV=y
CONFIG_LWIP_SNTP_UPDATE_DELAY=43200000
CONFIG_LWIP_SNTP_MAX_SERVERS=3
CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y

View File

@@ -5,7 +5,7 @@
#define NUKI_HUB_VERSION "9.08"
#define NUKI_HUB_VERSION_INT (uint32_t)908
#define NUKI_HUB_BUILD "unknownbuildnr"
#define NUKI_HUB_DATE "2025-01-20"
#define NUKI_HUB_DATE "2025-01-22"
#define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest"
#define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json"
@@ -24,6 +24,7 @@
#define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32c3.bin"
#define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32c3.bin"
#define NUKI_HUB_HW (char*)"ESP32-C3"
#define BOOT_BUTTON_GPIO (gpio_num_t)9
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
#if defined(CONFIG_SPIRAM_MODE_OCT)
#define GITHUB_LATEST_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32s3oct.bin"
@@ -41,6 +42,7 @@
#define GITHUB_LATEST_RELEASE_BINARY_URL_OTHER (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32s3.bin"
#define GITHUB_LATEST_UPDATER_BINARY_URL_OTHER (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_updater_esp32s3.bin"
#define NUKI_HUB_HW (char*)"ESP32-S3 (Octal PSRAM)"
#define BOOT_BUTTON_GPIO (gpio_num_t)0
#else
#define GITHUB_LATEST_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32s3.bin"
#define GITHUB_LATEST_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_updater_esp32s3.bin"
@@ -57,6 +59,7 @@
#define GITHUB_LATEST_RELEASE_BINARY_URL_OTHER (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32s3oct.bin"
#define GITHUB_LATEST_UPDATER_BINARY_URL_OTHER (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_updater_esp32s3oct.bin"
#define NUKI_HUB_HW (char*)"ESP32-S3"
#define BOOT_BUTTON_GPIO (gpio_num_t)0
#endif
#elif defined(CONFIG_IDF_TARGET_ESP32C6)
#define GITHUB_LATEST_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32c6.bin"
@@ -72,6 +75,7 @@
#define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32c6.bin"
#define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32c6.bin"
#define NUKI_HUB_HW (char*)"ESP32-C6"
#define BOOT_BUTTON_GPIO (gpio_num_t)9
#elif defined(CONFIG_IDF_TARGET_ESP32H2)
#define GITHUB_LATEST_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32h2.bin"
#define GITHUB_LATEST_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_updater_esp32h2.bin"
@@ -86,6 +90,7 @@
#define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32h2.bin"
#define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32h2.bin"
#define NUKI_HUB_HW (char*)"ESP32-H2"
#define BOOT_BUTTON_GPIO (gpio_num_t)9
#else
#if defined(CONFIG_FREERTOS_UNICORE)
#define GITHUB_LATEST_RELEASE_BINARY_URL "https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32-solo1.bin"
@@ -101,6 +106,7 @@
#define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32-solo1.bin"
#define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32-solo1.bin"
#define NUKI_HUB_HW (char*)"ESP32-SOLO1"
#define BOOT_BUTTON_GPIO (gpio_num_t)0
#else
#define GITHUB_LATEST_RELEASE_BINARY_URL "https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32.bin"
#define GITHUB_LATEST_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_updater_esp32.bin"
@@ -115,6 +121,7 @@
#define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32.bin"
#define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32.bin"
#define NUKI_HUB_HW (char*)"ESP32"
#define BOOT_BUTTON_GPIO (gpio_num_t)0
#endif
#endif

View File

@@ -122,6 +122,19 @@ void Gpio::init()
bool hasInputPin = false;
if (_inst->_preferences->getBool(preference_cred_bypass_boot_btn_enabled, false))
{
pinMode(BOOT_BUTTON_GPIO, INPUT);
}
if (_inst->_preferences->getInt(preference_cred_bypass_gpio_high, -1) > -1)
{
pinMode(_inst->_preferences->getInt(preference_cred_bypass_gpio_high, -1), INPUT);
}
if (_inst->_preferences->getInt(preference_cred_bypass_gpio_low, -1) > -1)
{
pinMode(_inst->_preferences->getInt(preference_cred_bypass_gpio_low, -1), INPUT);
}
for(const auto& entry : _inst->_pinConfiguration)
{
const auto it = std::find(_inst->availablePins().begin(), _inst->availablePins().end(), entry.pin);
@@ -242,6 +255,19 @@ const std::vector<int> Gpio::getDisabledPins() const
{
std::vector<int> disabledPins;
if (_preferences->getBool(preference_cred_bypass_boot_btn_enabled, false))
{
disabledPins.push_back(BOOT_BUTTON_GPIO);
}
if (_preferences->getInt(preference_cred_bypass_gpio_high, -1) > -1)
{
disabledPins.push_back(_preferences->getInt(preference_cred_bypass_gpio_high, -1));
}
if (_preferences->getInt(preference_cred_bypass_gpio_low, -1) > -1)
{
disabledPins.push_back(_preferences->getInt(preference_cred_bypass_gpio_low, -1));
}
switch(_preferences->getInt(preference_network_hardware, 0))
{
case 2:
@@ -333,7 +359,7 @@ const std::vector<int> Gpio::getDisabledPins() const
break;
}
Log->print(("GPIO Ethernet disabled pins:"));
Log->print(("GPIO Boot button and Ethernet disabled pins:"));
for_each_n(disabledPins.begin(), disabledPins.size(),
[](int x)
{

View File

@@ -88,6 +88,9 @@
#define preference_cred_session_lifetime_duo (char*)"credLfDuo"
#define preference_cred_session_lifetime_duo_remember (char*)"credLfDuoRmbr"
#define preference_cred_duo_approval (char*)"duoApprove"
#define preference_cred_bypass_boot_btn_enabled (char*)"bypassBtBtn"
#define preference_cred_bypass_gpio_high (char*)"bypassHigh"
#define preference_cred_bypass_gpio_low (char*)"bypassLow"
// CHANGE DOES NOT REQUIRE REBOOT TO TAKE EFFECT
#define preference_find_best_rssi (char*)"nwbestrssi"
@@ -237,6 +240,7 @@ inline void initPreferences(Preferences* preferences)
preferences->putBool(preference_enable_debug_mode, false);
preferences->putBool(preference_cred_duo_enabled, false);
preferences->putBool(preference_cred_duo_approval, false);
preferences->putBool(preference_cred_bypass_boot_btn_enabled, false);
preferences->putInt(preference_mqtt_broker_port, 1883);
preferences->putInt(preference_buffer_size, CHAR_BUFFER_SIZE);
@@ -260,6 +264,8 @@ inline void initPreferences(Preferences* preferences)
preferences->putInt(preference_cred_session_lifetime_remember, 720);
preferences->putInt(preference_cred_session_lifetime_duo, 3600);
preferences->putInt(preference_cred_session_lifetime_duo_remember, 720);
preferences->putInt(preference_cred_bypass_gpio_high, -1);
preferences->putInt(preference_cred_bypass_gpio_low, -1);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.begin();
@@ -519,7 +525,7 @@ private:
preference_lock_force_id, preference_lock_force_doorsensor, preference_lock_force_keypad, preference_opener_force_id, preference_opener_force_keypad, preference_nukihub_id,
preference_cred_duo_host, preference_cred_duo_ikey, preference_cred_duo_skey, preference_cred_duo_user, preference_cred_duo_enabled, preference_https_fqdn, preference_bypass_proxy,
preference_cred_session_lifetime, preference_cred_session_lifetime_remember, preference_cred_session_lifetime_duo, preference_cred_session_lifetime_duo_remember,
preference_cred_duo_approval
preference_cred_duo_approval, preference_cred_bypass_boot_btn_enabled, preference_cred_bypass_gpio_high, preference_cred_bypass_gpio_low
};
std::vector<char*> _redact =
{
@@ -535,7 +541,7 @@ private:
preference_publish_authdata, preference_publish_debug_info, preference_official_hybrid_enabled, preference_mqtt_hass_enabled, preference_retain_gpio,
preference_official_hybrid_actions, preference_official_hybrid_retry, preference_conf_info_enabled, preference_disable_non_json, preference_update_from_mqtt,
preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_webserial_enabled, preference_hass_device_discovery,
preference_keypad_check_code_enabled, preference_disable_network_not_connected, preference_find_best_rssi,
preference_keypad_check_code_enabled, preference_disable_network_not_connected, preference_find_best_rssi, preference_cred_bypass_boot_btn_enabled,
preference_debug_connect, preference_debug_communication, preference_debug_readable_data, preference_debug_hex_data, preference_debug_command, preference_connect_mode,
preference_lock_force_id, preference_lock_force_doorsensor, preference_lock_force_keypad, preference_opener_force_id, preference_opener_force_keypad, preference_mqtt_ssl_enabled,
preference_hybrid_reboot_on_disconnect, preference_lock_gemini_enabled, preference_enable_debug_mode, preference_cred_duo_enabled, preference_cred_duo_approval
@@ -556,7 +562,8 @@ private:
preference_ble_tx_power, preference_network_custom_mdc, preference_network_custom_clk, preference_network_custom_phy, preference_network_custom_addr,
preference_network_custom_irq, preference_network_custom_rst, preference_network_custom_cs, preference_network_custom_sck, preference_network_custom_miso,
preference_network_custom_mosi, preference_network_custom_pwr, preference_network_custom_mdio, preference_http_auth_type,
preference_cred_session_lifetime, preference_cred_session_lifetime_remember, preference_cred_session_lifetime_duo, preference_cred_session_lifetime_duo_remember
preference_cred_session_lifetime, preference_cred_session_lifetime_remember, preference_cred_session_lifetime_duo, preference_cred_session_lifetime_duo_remember,
preference_cred_bypass_gpio_high, preference_cred_bypass_gpio_low
};
std::vector<char*> _uintPrefs =
{

View File

@@ -16,6 +16,7 @@
#endif
#include <Update.h>
#include <DuoAuthLib.h>
#include "driver/gpio.h"
extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start");
extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end");
@@ -60,6 +61,15 @@ WebCfgServer::WebCfgServer(NukiNetwork* network, Preferences* preferences, bool
{
_duoEnabled = false;
}
else if (_preferences->getBool(preference_cred_bypass_boot_btn_enabled, false) || _preferences->getInt(preference_cred_bypass_gpio_high, -1) > -1 || _preferences->getInt(preference_cred_bypass_gpio_low, -1) > -1)
{
if (_preferences->getBool(preference_cred_bypass_boot_btn_enabled, false))
{
_bypassGPIO = true;
}
_bypassGPIOHigh = _preferences->getInt(preference_cred_bypass_gpio_high, -1);
_bypassGPIOLow = _preferences->getInt(preference_cred_bypass_gpio_low, -1);
}
}
if(str.length() > 0)
@@ -288,6 +298,30 @@ int WebCfgServer::doAuthentication(PsychicRequest *request)
}
else if (_duoEnabled && !isAuthenticated(request, true))
{
if (_bypassGPIO)
{
if (digitalRead(BOOT_BUTTON_GPIO) == LOW)
{
Log->print("Duo bypassed because boot button pressed");
return 4;
}
}
if (_bypassGPIOHigh > -1)
{
if (digitalRead(_bypassGPIOHigh) == HIGH)
{
Log->print("Duo bypassed because bypass GPIO pin pulled high");
return 4;
}
}
if (_bypassGPIOLow > -1)
{
if (digitalRead(_bypassGPIOLow) == LOW)
{
Log->print("Duo bypassed because bypass GPIO pin pulled low");
return 4;
}
}
return 3;
}
}
@@ -299,6 +333,30 @@ int WebCfgServer::doAuthentication(PsychicRequest *request)
}
else if (_duoEnabled && !isAuthenticated(request, true))
{
if (_bypassGPIO)
{
if (digitalRead(BOOT_BUTTON_GPIO) == LOW)
{
Log->print("Duo bypassed because boot button pressed");
return 4;
}
}
if (_bypassGPIOHigh > -1)
{
if (digitalRead(_bypassGPIOHigh) == HIGH)
{
Log->print("Duo bypassed because bypass GPIO pin pulled high");
return 4;
}
}
if (_bypassGPIOLow > -1)
{
if (digitalRead(_bypassGPIOLow) == LOW)
{
Log->print("Duo bypassed because bypass GPIO pin pulled low");
return 4;
}
}
return 3;
}
}
@@ -307,7 +365,7 @@ int WebCfgServer::doAuthentication(PsychicRequest *request)
return 4;
}
bool WebCfgServer::startDuoAuth()
bool WebCfgServer::startDuoAuth(char* pushType)
{
int64_t timeout = esp_timer_get_time() - (30 * 1000 * 1000L);
if(!_duoActiveRequest || timeout > _duoRequestTS)
@@ -320,6 +378,7 @@ bool WebCfgServer::startDuoAuth()
DuoAuthLib duoAuth;
bool duoRequestResult;
duoAuth.begin(duo_host, duo_ikey, duo_skey, &timeinfo);
duoAuth.setPushType(pushType);
duoRequestResult = duoAuth.pushAuth((char*)duo_user, true);
if(duoRequestResult == true)
@@ -346,24 +405,9 @@ int WebCfgServer::checkDuoAuth(PsychicRequest *request)
const char* duo_skey = _duoSkey.c_str();
const char* duo_user = _duoUser.c_str();
Log->println("Checking Duo auth");
if (request->hasParam("id")) {
Log->println("Found Duo ID");
const PsychicWebParameter* p = request->getParam("id");
String cookie2 = p->value();
Log->print("_duoActiveRequest: ");
Log->println(_duoActiveRequest ? "Yes" : "No");
Log->print("_duoCheckIP: ");
Log->println(_duoCheckIP);
Log->print("clientIP: ");
Log->println(request->client()->localIP().toString());
Log->print("cookie2: ");
Log->println(cookie2);
Log->print("_duoCheckId: ");
Log->println(_duoCheckId);
DuoAuthLib duoAuth;
if(_duoActiveRequest && _duoCheckIP == request->client()->localIP().toString() && cookie2 == _duoCheckId)
{
@@ -642,6 +686,10 @@ void WebCfgServer::initialize()
{
return buildDuoCheckHtml(request, resp);
}
else if (value == "coredump")
{
return buildCoredumpHtml(request, resp);
}
else if (value == "reboot")
{
String value = "";
@@ -686,6 +734,26 @@ void WebCfgServer::initialize()
}
else if (value == "export")
{
if(_preferences->getBool(preference_cred_duo_approval, false))
{
if (startDuoAuth((char*)"Approve Nuki Hub export"))
{
int duoResult = 2;
while (duoResult == 2)
{
duoResult = checkDuoApprove();
delay(2000);
esp_task_wdt_reset();
}
if (duoResult != 1)
{
return buildConfirmHtml(request, resp, "Duo approval failed, redirecting to main menu", 3, true);
}
}
}
return sendSettings(request, resp);
}
else if (value == "impexpcfg")
@@ -881,7 +949,7 @@ void WebCfgServer::initialize()
if(_preferences->getBool(preference_cred_duo_approval, false))
{
if (startDuoAuth())
if (startDuoAuth((char*)"Approve Nuki Hub setting change"))
{
int duoResult = 2;
@@ -894,9 +962,7 @@ void WebCfgServer::initialize()
if (duoResult != 1)
{
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/");
return buildConfirmHtml(request, resp, "Duo approval failed, redirecting to main menu", 3, true);
}
}
}
@@ -1856,9 +1922,38 @@ esp_err_t WebCfgServer::buildDuoCheckHtml(PsychicRequest *request, PsychicRespon
return resp->send();
}
esp_err_t WebCfgServer::buildCoredumpHtml(PsychicRequest *request, PsychicResponse* resp)
{
if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed");
}
else
{
File file = SPIFFS.open("/coredump.hex", "r");
if (!file || file.isDirectory()) {
Log->println("coredump.hex not found");
}
else
{
PsychicFileResponse response(resp, file, "coredump.hex");
String name = "coredump.txt";
char buf[26 + name.length()];
snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", name.c_str());
response.addHeader("Content-Disposition", buf);
return response.send();
}
}
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/");
}
esp_err_t WebCfgServer::buildDuoHtml(PsychicRequest *request, PsychicResponse* resp)
{
bool duo = startDuoAuth();
bool duo = startDuoAuth((char*)"Approve Nuki Hub login");
if (!duo)
{
@@ -1895,10 +1990,10 @@ esp_err_t WebCfgServer::buildDuoHtml(PsychicRequest *request, PsychicResponse* r
response.beginSend();
response.print("<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
response.print("<style>.container{border:3px solid #f1f1f1; max-width: 400px; padding:16px}</style>");
response.print((String)"<script>let intervalId; window.onload = function() { updateInfo(); intervalId = setInterval(updateInfo, 2000); }; function updateInfo() { var request = new XMLHttpRequest(); request.open('GET', '/get?page=duocheck&id=" + _duoCheckId + "', true); request.onload = () => { const obj = request.responseText; if (obj == \"1\" || obj == \"0\") { clearInterval(intervalId); window.location.href = \"/\"; } }; request.send(); }</script>");
response.print((String)"<script>let intervalId; window.onload = function() { updateInfo(); intervalId = setInterval(updateInfo, 2000); }; function updateInfo() { var request = new XMLHttpRequest(); request.open('GET', '/get?page=duocheck&id=" + _duoCheckId + "', true); request.onload = () => { const obj = request.responseText; if (obj == \"1\" || obj == \"0\") { clearInterval(intervalId); if (obj == \"1\") { document.getElementById('duoresult').innerHTML = 'Login approved<br>Redirecting...'; setTimeout(function() { window.location.href = \"/\"; }, 2000); } else { document.getElementById('duoresult').innerHTML = 'Login failed<br>Refresh to retry'; } } }; request.send(); }</script>");
response.print("</head><body><center><h2>Nuki Hub login</h2>");
response.print("<div class=\"container\">Duo Push sent<br><br>");
response.print("Please confirm login in the Duo app<br><br></div>");
response.print("Please confirm login in the Duo app<br><br><div id=\"duoresult\"></div></div>");
response.print("</div>");
response.print("</center></body></html>");
@@ -2847,6 +2942,36 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
newMFA = true;
}
}
else if(key == "DUOBYPASS")
{
if(_preferences->getBool(preference_cred_bypass_boot_btn_enabled, false) != (value == "1"))
{
_preferences->putBool(preference_cred_bypass_boot_btn_enabled, (value == "1"));
Log->print(("Setting changed: "));
Log->println(key);
configChanged = true;
}
}
else if(key == "DUOBYPASSHIGH")
{
if(_preferences->getInt(preference_cred_bypass_gpio_high, -1) != value.toInt())
{
_preferences->putInt(preference_cred_bypass_gpio_high, value.toInt());
Log->print(("Setting changed: "));
Log->println(key);
configChanged = true;
}
}
else if(key == "DUOBYPASSLOW")
{
if(_preferences->getInt(preference_cred_bypass_gpio_low, -1) != value.toInt())
{
_preferences->putInt(preference_cred_bypass_gpio_low, value.toInt());
Log->print(("Setting changed: "));
Log->println(key);
configChanged = true;
}
}
else if(key == "DUOAPPROVAL")
{
if(_preferences->getBool(preference_cred_duo_approval, false) != (value == "1"))
@@ -4761,6 +4886,7 @@ esp_err_t WebCfgServer::buildImportExportHtml(PsychicRequest *request, PsychicRe
}
#endif
response.print("<br><br><button title=\"Export MQTT SSL CA, client certificate and client key\" onclick=\" window.open('/get?page=export&type=mqtts'); return false;\">Export MQTT SSL CA, client certificate and client key</button>");
response.print("<br><br><button title=\"Export Coredump\" onclick=\" window.open('/get?page=coredump'); return false;\">Export Coredump</button>");
response.print("</div></body></html>");
return response.endSend();
}
@@ -4919,6 +5045,9 @@ esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request, PsychicResponse*
printInputField(&response, "CREDTRUSTPROXY", "Bypass authentication for reverse proxy with IP", _preferences->getString(preference_bypass_proxy, "").c_str(), 255, "");
printCheckBox(&response, "DUOENA", "Duo Push authentication enabled", _preferences->getBool(preference_cred_duo_enabled, false), "");
printCheckBox(&response, "DUOAPPROVAL", "Require Duo Push authentication for all sensitive Nuki Hub operations (changing/exporting settings)", _preferences->getBool(preference_cred_duo_approval, false), "");
printCheckBox(&response, "DUOBYPASS", "Bypass Duo Push authentication by pressing the BOOT button while logging in", _preferences->getBool(preference_cred_bypass_boot_btn_enabled, false), "");
printInputField(&response, "DUOBYPASSHIGH", "Bypass Duo Push authentication by pulling GPIO High", _preferences->getInt(preference_cred_bypass_gpio_high, -1), 2, "");
printInputField(&response, "DUOBYPASSLOW", "Bypass Duo Push authentication by pulling GPIO Low", _preferences->getInt(preference_cred_bypass_gpio_low, -1), 2, "");
printInputField(&response, "DUOHOST", "Duo API hostname", "*", 255, "", true, false);
printInputField(&response, "DUOIKEY", "Duo integration key", "*", 255, "", true, false);
printInputField(&response, "DUOSKEY", "Duo secret key", "*", 255, "", true, false);

View File

@@ -103,7 +103,7 @@ private:
int checkDuoAuth(PsychicRequest *request);
int checkDuoApprove();
bool startDuoAuth();
bool startDuoAuth(char* pushType = (char*)"");
void saveSessions(bool duo = false);
void loadSessions(bool duo = false);
void clearSessions();
@@ -111,6 +111,7 @@ private:
bool isAuthenticated(PsychicRequest *request, bool duo = false);
bool processLogin(PsychicRequest *request, PsychicResponse* resp);
int doAuthentication(PsychicRequest *request);
esp_err_t buildCoredumpHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t buildLoginHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t buildDuoHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t buildDuoCheckHtml(PsychicRequest *request, PsychicResponse* resp);
@@ -149,6 +150,9 @@ private:
struct tm timeinfo;
bool _duoActiveRequest;
bool _duoEnabled = false;
bool _bypassGPIO = false;
int _bypassGPIOHigh = -1;
int _bypassGPIOLow = -1;
int64_t _duoRequestTS = 0;
String _duoTransactionId;
String _duoHost;

View File

@@ -11,10 +11,11 @@
#include "hal/wdt_hal.h"
#include "esp_chip_info.h"
#include "esp_netif_sntp.h"
#ifdef CONFIG_SOC_SPIRAM_SUPPORTED
#include "esp_psram.h"
#include "esp_core_dump.h"
#include "FS.h"
#include "SPIFFS.h"
#ifdef CONFIG_SOC_SPIRAM_SUPPORTED
#include "esp_psram.h"
#endif
#ifndef NUKI_HUB_UPDATER
@@ -88,6 +89,7 @@ RTC_NOINIT_ATTR bool forceEnableWebServer;
RTC_NOINIT_ATTR bool disableNetwork;
RTC_NOINIT_ATTR bool wifiFallback;
RTC_NOINIT_ATTR bool ethCriticalFailure;
bool coredumpPrinted = true;
int lastHTTPeventId = -1;
bool doOta = false;
@@ -507,6 +509,93 @@ void setupTasks(bool ota)
}
}
void logCoreDump()
{
coredumpPrinted = false;
delay(500);
Serial.println("Printing coredump and saving to coredump.hex on SPIFFS");
size_t size = 0;
size_t address = 0;
if (esp_core_dump_image_get(&address, &size) == ESP_OK)
{
const esp_partition_t *pt = NULL;
pt = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, "coredump");
if (pt != NULL)
{
uint8_t bf[256];
char str_dst[640];
int16_t toRead;
if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed");
}
File file = SPIFFS.open("/coredump.hex", FILE_WRITE);
if (!file) {
Log->println("Failed to open /coredump.hex for writing");
}
else
{
file.printf("%s\r\n", NUKI_HUB_HW);
file.printf("%s\r\n", NUKI_HUB_BUILD);
}
Serial.printf("%s\r\n", NUKI_HUB_HW);
Serial.printf("%s\r\n", NUKI_HUB_BUILD);
for (int16_t i = 0; i < (size/256)+1; i++)
{
strcpy(str_dst, "");
toRead = (size - i*256) > 256 ? 256 : (size - i*256);
esp_err_t er = esp_partition_read(pt, i*256, bf, toRead);
if (er != ESP_OK)
{
Serial.printf("FAIL [%x]", er);
break;
}
for (int16_t j = 0; j < 256; j++)
{
char str_tmp[2];
if (bf[j] <= 0x0F)
{
sprintf(str_tmp, "0%x", bf[j]);
}
else
{
sprintf(str_tmp, "%x", bf[j]);
}
strcat(str_dst, str_tmp);
}
Serial.printf("%s", str_dst);
if (file) {
file.printf("%s", str_dst);
}
}
Serial.println("");
if (file) {
file.println("");
file.close();
}
}
else
{
Serial.println("Partition NULL");
}
}
else
{
Serial.println("esp_core_dump_image_get() FAIL");
}
coredumpPrinted = true;
}
void setup()
{
//Set Log level to error for all TAGS
@@ -529,10 +618,18 @@ void setup()
preferences = new Preferences();
preferences->begin("nukihub", false);
initPreferences(preferences);
uint8_t partitionType = checkPartition();
initializeRestartReason();
if(esp_reset_reason() == esp_reset_reason_t::ESP_RST_PANIC ||
esp_reset_reason() == esp_reset_reason_t::ESP_RST_INT_WDT ||
esp_reset_reason() == esp_reset_reason_t::ESP_RST_TASK_WDT ||
esp_reset_reason() == esp_reset_reason_t::ESP_RST_WDT)
{
logCoreDump();
}
uint8_t partitionType = checkPartition();
//default disableNetwork RTC_ATTR to false on power-on
if(espRunning != 1)
{