Merge pull request #628 from iranl/mfa-import-export-improvements
Update esp-nimble and pioarduino 3.1.3, various improvements and fixes for HA, SPIFFS and TOTP, add self-signed SSL certificate generator, add bypass MFA, add admin key, add build for GL-S10
This commit is contained in:
2
.github/workflows/beta.yml
vendored
2
.github/workflows/beta.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
board: [esp32, esp32-s3, esp32-s3-oct, esp32-c3, esp32-c6, esp32-h2, esp32-solo1]
|
||||
board: [esp32, esp32-s3, esp32-s3-oct, esp32-c3, esp32-c6, esp32-h2, esp32-solo1, esp32-gl-s10]
|
||||
build: [release]
|
||||
env:
|
||||
BOARD: ${{ matrix.board }}
|
||||
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
board: [esp32, esp32-s3, esp32-s3-oct, esp32-c3, esp32-c6, esp32-h2, esp32-solo1]
|
||||
board: [esp32, esp32-s3, esp32-s3-oct, esp32-c3, esp32-c6, esp32-h2, esp32-solo1, esp32-gl-s10]
|
||||
build: [release]
|
||||
env:
|
||||
BOARD: ${{ matrix.board }}
|
||||
|
||||
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
board: [esp32, esp32-s3, esp32-s3-oct, esp32-c3, esp32-c6, esp32-h2, esp32-solo1]
|
||||
board: [esp32, esp32-s3, esp32-s3-oct, esp32-c3, esp32-c6, esp32-h2, esp32-solo1, esp32-gl-s10]
|
||||
build: [release]
|
||||
env:
|
||||
BOARD: ${{ matrix.board }}
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
board: [esp32, esp32-s3, esp32-s3-oct, esp32-c3, esp32-c6, esp32-h2, esp32-solo1]
|
||||
board: [esp32, esp32-s3, esp32-s3-oct, esp32-c3, esp32-c6, esp32-h2, esp32-solo1, esp32-gl-s10]
|
||||
build: [release]
|
||||
env:
|
||||
BOARD: ${{ matrix.board }}
|
||||
|
||||
@@ -15,7 +15,7 @@ Feel free to join us on Discord: https://discord.gg/9nPq85bP4p
|
||||
## Supported devices
|
||||
|
||||
<b>Supported ESP32 devices:</b>
|
||||
- Nuki Hub is compiled against all ESP32 models with Wi-Fi and Bluetooh Low Energy (BLE) which are supported by ESP-IDF 5.3.2 and Arduino Core 3.1.0.
|
||||
- Nuki Hub is compiled against all ESP32 models with Wi-Fi and Bluetooh Low Energy (BLE) which are supported by ESP-IDF 5.3.2 and Arduino Core 3.1.1.
|
||||
- Tested stable builds are provided for the ESP32, ESP32-S3, ESP32-C3, ESP32-C6 and ESP32-H2.
|
||||
- Untested builds are provided for the ESP32-Solo1 (as the developers don't own one).
|
||||
|
||||
@@ -333,6 +333,8 @@ Note: All of the following requires the Nuki security code / PIN to be set, see
|
||||
- Duo secret key: Set to the Duo secret key
|
||||
- Duo user: Set to the Duo user that you want to receive the push notification
|
||||
- TOTP Secret Key: Set a TOTP secret key to enable TOTP MFA. Enter the TOTP secret key in an authenticator application (Password manager, Microsoft/Google Authenticator etc.) to generate TOTP codes.
|
||||
- One-time MFA Bypass: Set a 32 character long alphanumeric string that can be used as a one-time MFA bypass when the ESP32 is unable to sync it's time and TOTP and Duo are unavailable as a result.
|
||||
- Admin key: Set a 32 character long alphanumeric string that can be used in combination with a TOTP code to export and import settings without needing to log in (for use with automated systems).
|
||||
- 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.
|
||||
|
||||
37
boards/nuki-esp32gls10.json
Normal file
37
boards/nuki-esp32gls10.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino":{
|
||||
"ldscript": "esp32_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "40000000L",
|
||||
"flash_mode": "dio",
|
||||
"mcu": "esp32",
|
||||
"variant": "doitESP32devkitV1"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi",
|
||||
"bluetooth",
|
||||
"ethernet",
|
||||
"can"
|
||||
],
|
||||
"debug": {
|
||||
"openocd_board": "esp-wroom-32.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino",
|
||||
"espidf"
|
||||
],
|
||||
"name": "DOIT ESP32 DEVKIT V1",
|
||||
"upload": {
|
||||
"flash_size": "4MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 4194304,
|
||||
"require_upload_port": true,
|
||||
"speed": 460800
|
||||
},
|
||||
"url": "http://www.doit.am/",
|
||||
"vendor": "DOIT"
|
||||
}
|
||||
@@ -2,3 +2,4 @@
|
||||
# List of Authors
|
||||
|
||||
Gary Oppel <gaoppel@cisco.com>
|
||||
iranl <25727444+iranl@users.noreply.github.com>
|
||||
@@ -5,3 +5,8 @@ v1.0.0 (08/26/2020)
|
||||
------
|
||||
|
||||
* Initial Library Release
|
||||
|
||||
v1.1.0 (01/20/2025)
|
||||
------
|
||||
|
||||
* Enable using ESP32 CA Certificate bundle
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
name=Duo Auth Library
|
||||
version=1.0.0
|
||||
author=Gary Oppel
|
||||
maintainer=Gary Oppel
|
||||
version=1.1.0
|
||||
author=iranl
|
||||
maintainer=iranl
|
||||
sentence=Enables Duo Authentication within your ESP32 Wi-Fi Projects
|
||||
paragraph=Extends Duo Authentication API's for Push, Passcode, and Asynchronous Push Authentication requests.
|
||||
category=Other
|
||||
url=https://github.com/CiscoDevNet/Arduino-DuoAuthLibrary-ESP32
|
||||
url=https://github.com/technyon/nuki_hub/lib/DuoAuthLibrary
|
||||
architectures=esp32
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
*@license
|
||||
*
|
||||
*Copyright 2020 Cisco Systems, Inc. or its affiliates
|
||||
*Modifications copyright (C) 2025 iranl / Nuki Hub
|
||||
*
|
||||
*Licensed under the Apache License, Version 2.0 (the "License");
|
||||
*you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +25,11 @@
|
||||
* @url https://github.com/CiscoDevNet/Arduino-DuoAuthLibrary-ESP32
|
||||
* @version 1.0.0
|
||||
* @author Gary Oppel <gaoppel@cisco.com>
|
||||
|
||||
* @url https://github.com/technyon/nuki_hub/lib/DuoAuthLibrary
|
||||
* @version 1.1.0
|
||||
* @author iranl <25727444+iranl@users.noreply.github.com>
|
||||
* Modified to enable using ESP32 CA Certificate bundle
|
||||
*/
|
||||
|
||||
//Include DuoAuthLib Library Header
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
*@license
|
||||
*
|
||||
*Copyright 2020 Cisco Systems, Inc. or its affiliates
|
||||
*Modifications copyright (C) 2025 iranl / Nuki Hub
|
||||
*
|
||||
*Licensed under the Apache License, Version 2.0 (the "License");
|
||||
*you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +25,11 @@
|
||||
* @url https://github.com/CiscoDevNet/Arduino-DuoAuthLibrary-ESP32
|
||||
* @version 1.0.0
|
||||
* @author Gary Oppel <gaoppel@cisco.com>
|
||||
|
||||
* @url https://github.com/technyon/nuki_hub/lib/DuoAuthLibrary
|
||||
* @version 1.1.0
|
||||
* @author iranl <25727444+iranl@users.noreply.github.com>
|
||||
* Modified to enable using ESP32 CA Certificate bundle
|
||||
*/
|
||||
|
||||
//Verify that the Duo Auth Library descriptor is only included once
|
||||
|
||||
@@ -1 +1 @@
|
||||
# Espressif ESP32 Partition Table
|
||||
# Espressif ESP32 Partition Table
|
||||
|
@@ -9,6 +9,8 @@ def get_board_name(env):
|
||||
|
||||
if env.get('BOARD') == 'nuki-esp32solo1':
|
||||
board = 'esp32solo1'
|
||||
elif env.get('BOARD') == 'nuki-esp32gls10':
|
||||
board = 'esp32gls10'
|
||||
elif env.get('BOARD') == 'nuki-esp32-s3-oct':
|
||||
board = 'esp32s3oct'
|
||||
return board
|
||||
|
||||
@@ -13,7 +13,7 @@ default_envs = esp32
|
||||
boards_dir = boards
|
||||
|
||||
[env]
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.13/platform-espressif32.zip
|
||||
platform_packages =
|
||||
framework = arduino, espidf
|
||||
board_build.embed_txtfiles =
|
||||
@@ -78,6 +78,13 @@ build_flags =
|
||||
-DCONFIG_NIMBLE_CPP_LOG_LEVEL=0
|
||||
-DCONFIG_BT_NIMBLE_LOG_LEVEL=0
|
||||
|
||||
[env:esp32-gl-s10]
|
||||
extends = env:esp32
|
||||
board = nuki-esp32gls10
|
||||
board_build.cmake_extra_args =
|
||||
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.release.defaults;sdkconfig.defaults.esp32;sdkconfig.gls10.defaults"
|
||||
-DNUKI_TARGET_GL_S10=y
|
||||
|
||||
[env:esp32-c3]
|
||||
extends = env:esp32
|
||||
board = esp32-c3-devkitc-02
|
||||
@@ -136,6 +143,18 @@ build_flags =
|
||||
-DCONFIG_BT_NIMBLE_LOG_LEVEL=0
|
||||
-DDEBUG_NUKIHUB
|
||||
|
||||
[env:esp32-gl-s10_dbg]
|
||||
extends = env:esp32-gl-s10
|
||||
custom_build = debug
|
||||
board_build.cmake_extra_args =
|
||||
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.debug.defaults;sdkconfig.defaults.esp32;sdkconfig.gls10.defaults"
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
|
||||
-DCONFIG_NIMBLE_CPP_LOG_LEVEL=0
|
||||
-DCONFIG_BT_NIMBLE_LOG_LEVEL=0
|
||||
-DDEBUG_NUKIHUB
|
||||
|
||||
[env:esp32-c3_dbg]
|
||||
extends = env:esp32-c3
|
||||
custom_build = debug
|
||||
|
||||
@@ -29,6 +29,13 @@ e000 boot_app0.bin
|
||||
10000 nuki_hub_esp32.bin
|
||||
280000 nuki_hub_updater_esp32.bin
|
||||
|
||||
ESP32-GL-S10
|
||||
e000 boot_app0.bin
|
||||
1000 bootloader.bin
|
||||
8000 nuki_hub.partitions.bin
|
||||
10000 nuki_hub_esp32.bin
|
||||
280000 nuki_hub_updater_esp32.bin
|
||||
|
||||
ESP32-S3
|
||||
e000 boot_app0.bin
|
||||
0 bootloader.bin
|
||||
@@ -87,6 +94,10 @@ As an alternative to the Download Tools, you can also use the esptool from the E
|
||||
|
||||
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq keep --flash_size detect 0xe000 boot_app0.bin 0x1000 bootloader.bin 0x10000 nuki_hub_esp32.bin 0x280000 nuki_hub_updater_esp32.bin 0x8000 nuki_hub.partitions.bin
|
||||
|
||||
## ESP32-GL-S10
|
||||
|
||||
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq keep --flash_size detect 0xe000 boot_app0.bin 0x1000 bootloader.bin 0x10000 nuki_hub_esp32.bin 0x280000 nuki_hub_updater_esp32.bin 0x8000 nuki_hub.partitions.bin
|
||||
|
||||
## ESP32-S3
|
||||
|
||||
esptool.py --chip esp32s3 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq keep --flash_size detect 0xe000 boot_app0.bin 0x0 bootloader.bin 0x10000 nuki_hub_esp32s3.bin 0x280000 nuki_hub_updater_esp32s3.bin 0x8000 nuki_hub.partitions.bin
|
||||
|
||||
@@ -3,6 +3,7 @@ CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE=y
|
||||
CONFIG_SPIFFS_GC_MAX_RUNS=512
|
||||
|
||||
# ARDUINO
|
||||
CONFIG_AUTOSTART_ARDUINO=y
|
||||
@@ -39,7 +40,7 @@ CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y
|
||||
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
|
||||
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE=y
|
||||
CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE=y
|
||||
CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH="resources/github_root_ca.pem"
|
||||
CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH="resources/root_ca.pem"
|
||||
CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
|
||||
|
||||
# RTC WDT
|
||||
|
||||
1
sdkconfig.gls10.defaults
Normal file
1
sdkconfig.gls10.defaults
Normal file
@@ -0,0 +1 @@
|
||||
CONFIG_D0WD_PSRAM_CLK_IO=6
|
||||
17
src/Config.h
17
src/Config.h
@@ -5,7 +5,7 @@
|
||||
#define NUKI_HUB_VERSION "9.09"
|
||||
#define NUKI_HUB_VERSION_INT (uint32_t)909
|
||||
#define NUKI_HUB_BUILD "unknownbuildnr"
|
||||
#define NUKI_HUB_DATE "2025-02-10"
|
||||
#define NUKI_HUB_DATE "2025-02-18"
|
||||
|
||||
#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"
|
||||
@@ -107,6 +107,21 @@
|
||||
#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
|
||||
#elif defined(NUKI_TARGET_GL_S10)
|
||||
#define GITHUB_LATEST_RELEASE_BINARY_URL "https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32-gl-s10.bin"
|
||||
#define GITHUB_LATEST_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_updater_esp32.bin"
|
||||
#define GITHUB_BETA_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/beta/nuki_hub_esp32-gl-s10.bin"
|
||||
#define GITHUB_BETA_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/beta/nuki_hub_updater_esp32.bin"
|
||||
#define GITHUB_MASTER_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/master/nuki_hub_esp32-gl-s10.bin"
|
||||
#define GITHUB_MASTER_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/master/nuki_hub_updater_esp32.bin"
|
||||
#define GITHUB_LATEST_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/nuki_hub_esp32-gl-s10.bin"
|
||||
#define GITHUB_LATEST_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/nuki_hub_updater_esp32.bin"
|
||||
#define GITHUB_BETA_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_esp32-gl-s10.bin"
|
||||
#define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32.bin"
|
||||
#define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32-gl-s10.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-GL-S10"
|
||||
#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"
|
||||
|
||||
@@ -216,12 +216,12 @@ void HomeAssistantDiscovery::publishHASSNukiHubConfig()
|
||||
"",
|
||||
{ { (char*)"en", (char*)"true" }});
|
||||
|
||||
// Nuki Hub Webserver enabled
|
||||
// Webserver enabled
|
||||
publishHassTopic("switch",
|
||||
"webserver",
|
||||
_nukiHubUidString,
|
||||
"_webserver",
|
||||
"Nuki Hub webserver enabled",
|
||||
"Webserver enabled",
|
||||
_hostname.c_str(),
|
||||
_baseTopic.c_str(),
|
||||
String("~") + mqtt_topic_webserver_state,
|
||||
@@ -317,12 +317,12 @@ void HomeAssistantDiscovery::publishHASSNukiHubConfig()
|
||||
{(char*)"ic", (char*)"mdi:counter"}
|
||||
});
|
||||
|
||||
// Nuki Hub restart reason
|
||||
// Restart reason
|
||||
publishHassTopic("sensor",
|
||||
"nuki_hub_restart_reason",
|
||||
_nukiHubUidString,
|
||||
"_nuki_hub_restart_reason",
|
||||
"Nuki Hub restart reason",
|
||||
"Restart reason",
|
||||
_hostname.c_str(),
|
||||
_baseTopic.c_str(),
|
||||
String("~") + mqtt_topic_restart_reason_fw,
|
||||
@@ -333,12 +333,12 @@ void HomeAssistantDiscovery::publishHASSNukiHubConfig()
|
||||
"",
|
||||
{ { (char*)"en", (char*)"true" }});
|
||||
|
||||
// Nuki Hub restart reason ESP
|
||||
// Restart reason ESP
|
||||
publishHassTopic("sensor",
|
||||
"nuki_hub_restart_reason_esp",
|
||||
_nukiHubUidString,
|
||||
"_nuki_hub_restart_reason_esp",
|
||||
"Nuki Hub restart reason ESP",
|
||||
"Restart reason ESP",
|
||||
_hostname.c_str(),
|
||||
_baseTopic.c_str(),
|
||||
String("~") + mqtt_topic_restart_reason_esp,
|
||||
@@ -351,12 +351,12 @@ void HomeAssistantDiscovery::publishHASSNukiHubConfig()
|
||||
|
||||
if(_checkUpdates)
|
||||
{
|
||||
// NUKI Hub latest
|
||||
// Nuki Hub latest
|
||||
publishHassTopic("sensor",
|
||||
"nuki_hub_latest",
|
||||
_nukiHubUidString,
|
||||
"_nuki_hub_latest",
|
||||
"NUKI Hub latest",
|
||||
"Nuki Hub latest",
|
||||
_hostname.c_str(),
|
||||
_baseTopic.c_str(),
|
||||
String("~") + mqtt_topic_info_nuki_hub_latest,
|
||||
@@ -370,7 +370,7 @@ void HomeAssistantDiscovery::publishHASSNukiHubConfig()
|
||||
{(char*)"ic", (char*)"mdi:counter"}
|
||||
});
|
||||
|
||||
// NUKI Hub update
|
||||
// Nuki Hub update
|
||||
char latest_version_topic[250];
|
||||
_baseTopic.toCharArray(latest_version_topic,_baseTopic.length() + 1);
|
||||
strcat(latest_version_topic, mqtt_topic_info_nuki_hub_latest);
|
||||
@@ -381,7 +381,7 @@ void HomeAssistantDiscovery::publishHASSNukiHubConfig()
|
||||
"nuki_hub_update",
|
||||
_nukiHubUidString,
|
||||
"_nuki_hub_update",
|
||||
"NUKI Hub firmware update",
|
||||
"Nuki Hub firmware update",
|
||||
_hostname.c_str(),
|
||||
_baseTopic.c_str(),
|
||||
String("~") + mqtt_topic_info_nuki_hub_version,
|
||||
@@ -403,7 +403,7 @@ void HomeAssistantDiscovery::publishHASSNukiHubConfig()
|
||||
"nuki_hub_update",
|
||||
_nukiHubUidString,
|
||||
"_nuki_hub_update",
|
||||
"NUKI Hub firmware update",
|
||||
"Nuki Hub firmware update",
|
||||
_hostname.c_str(),
|
||||
_baseTopic.c_str(),
|
||||
String("~") + mqtt_topic_info_nuki_hub_version,
|
||||
@@ -427,12 +427,12 @@ void HomeAssistantDiscovery::publishHASSNukiHubConfig()
|
||||
removeHassTopic((char*)"update", (char*)"nuki_hub_update", _nukiHubUidString);
|
||||
}
|
||||
|
||||
// Nuki Hub IP Address
|
||||
// IP Address
|
||||
publishHassTopic("sensor",
|
||||
"nuki_hub_ip",
|
||||
_nukiHubUidString,
|
||||
"_nuki_hub_ip",
|
||||
"Nuki Hub IP",
|
||||
"IP",
|
||||
_hostname.c_str(),
|
||||
_baseTopic.c_str(),
|
||||
String("~") + mqtt_topic_info_nuki_hub_ip,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "ImportExport.h"
|
||||
#include "EspMillis.h"
|
||||
#include "SPIFFS.h"
|
||||
#include "Logger.h"
|
||||
#include "PreferencesKeys.h"
|
||||
@@ -38,6 +39,8 @@ void ImportExport::readSettings()
|
||||
|
||||
_totpKey = _preferences->getString(preference_totp_secret, "");
|
||||
_totpEnabled = _totpKey.length() > 0;
|
||||
_bypassKey = _preferences->getString(preference_bypass_secret, "");
|
||||
_bypassEnabled = _bypassKey.length() > 0;
|
||||
}
|
||||
|
||||
bool ImportExport::getDuoEnabled()
|
||||
@@ -50,6 +53,11 @@ bool ImportExport::getTOTPEnabled()
|
||||
return _totpEnabled;
|
||||
}
|
||||
|
||||
bool ImportExport::getBypassEnabled()
|
||||
{
|
||||
return _bypassEnabled;
|
||||
}
|
||||
|
||||
bool ImportExport::getBypassGPIOEnabled()
|
||||
{
|
||||
return _bypassGPIO;
|
||||
@@ -268,10 +276,18 @@ int ImportExport::checkDuoApprove()
|
||||
|
||||
bool ImportExport::checkTOTP(String* totpKey)
|
||||
{
|
||||
String key(totpKey->c_str());
|
||||
|
||||
if(_totpEnabled)
|
||||
{
|
||||
if((pow(_invalidCount, 5) + _lastCodeCheck) > espMillis())
|
||||
{
|
||||
_lastCodeCheck = espMillis();
|
||||
return false;
|
||||
}
|
||||
|
||||
_lastCodeCheck = espMillis();
|
||||
|
||||
String key(totpKey->c_str());
|
||||
|
||||
time_t now;
|
||||
time(&now);
|
||||
int totpTime = -60;
|
||||
@@ -282,16 +298,42 @@ bool ImportExport::checkTOTP(String* totpKey)
|
||||
|
||||
if(key.toInt() == key2.toInt())
|
||||
{
|
||||
_invalidCount = 0;
|
||||
Log->println("Successful TOTP MFA Auth");
|
||||
return true;
|
||||
}
|
||||
totpTime += 30;
|
||||
}
|
||||
_invalidCount++;
|
||||
Log->println("Failed TOTP MFA Auth");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImportExport::checkBypass(String bypass)
|
||||
{
|
||||
if(_bypassEnabled)
|
||||
{
|
||||
if((pow(_invalidCount2, 5) + _lastCodeCheck2) > espMillis())
|
||||
{
|
||||
_lastCodeCheck2 = espMillis();
|
||||
return false;
|
||||
}
|
||||
|
||||
_lastCodeCheck2 = espMillis();
|
||||
|
||||
if(bypass == _bypassKey)
|
||||
{
|
||||
_invalidCount2 = 0;
|
||||
Log->println("Successful Bypass MFA Auth");
|
||||
return true;
|
||||
}
|
||||
_invalidCount2++;
|
||||
Log->println("Failed Bypass MFA Auth");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImportExport::exportHttpsJson(JsonDocument &json)
|
||||
{
|
||||
if (!SPIFFS.begin(true)) {
|
||||
@@ -427,6 +469,18 @@ void ImportExport::exportNukiHubJson(JsonDocument &json, bool redacted, bool pai
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(strcmp(key, preference_totp_secret) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(strcmp(key, preference_bypass_secret) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(strcmp(key, preference_admin_secret) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(!redacted) if(std::find(redactedPrefs.begin(), redactedPrefs.end(), key) != redactedPrefs.end())
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -16,7 +16,9 @@ public:
|
||||
int checkDuoApprove();
|
||||
bool startDuoAuth(char* pushType = (char*)"");
|
||||
bool getTOTPEnabled();
|
||||
bool getBypassEnabled();
|
||||
bool checkTOTP(String* totpKey);
|
||||
bool checkBypass(String bypass);
|
||||
bool getDuoEnabled();
|
||||
bool getBypassGPIOEnabled();
|
||||
int getBypassGPIOHigh();
|
||||
@@ -27,11 +29,17 @@ public:
|
||||
JsonDocument _duoSessions;
|
||||
JsonDocument _totpSessions;
|
||||
JsonDocument _sessionsOpts;
|
||||
JsonDocument _bypassSessions;
|
||||
int64_t _lastCodeCheck = 0;
|
||||
int64_t _lastCodeCheck2 = 0;
|
||||
int _invalidCount = 0;
|
||||
int _invalidCount2 = 0;
|
||||
private:
|
||||
void saveSessions();
|
||||
Preferences* _preferences;
|
||||
struct tm timeinfo;
|
||||
bool _totpEnabled = false;
|
||||
bool _bypassEnabled = false;
|
||||
bool _duoActiveRequest;
|
||||
bool _duoEnabled = false;
|
||||
bool _bypassGPIO = false;
|
||||
@@ -46,5 +54,6 @@ private:
|
||||
String _duoCheckId;
|
||||
String _duoCheckIP;
|
||||
String _totpKey;
|
||||
String _bypassKey;
|
||||
};
|
||||
|
||||
|
||||
@@ -385,6 +385,16 @@ bool NukiNetwork::update()
|
||||
int64_t ts = espMillis();
|
||||
_device->update();
|
||||
|
||||
if(_importExport->getTOTPEnabled() && _importExport->_invalidCount > 0 && (ts - (120000 * _importExport->_invalidCount)) > _importExport->_lastCodeCheck)
|
||||
{
|
||||
_importExport->_invalidCount--;
|
||||
}
|
||||
|
||||
if(_importExport->getBypassEnabled() && _importExport->_invalidCount2 > 0 && (ts - (120000 * _importExport->_invalidCount2)) > _importExport->_lastCodeCheck2)
|
||||
{
|
||||
_importExport->_invalidCount2--;
|
||||
}
|
||||
|
||||
if(disableNetwork || !_mqttEnabled || _device->isApOpen())
|
||||
{
|
||||
return false;
|
||||
@@ -1093,7 +1103,7 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
|
||||
{
|
||||
if(_preferences->getBool(preference_cred_duo_approval, false) && (_importExport->getTOTPEnabled() || _importExport->getDuoEnabled()))
|
||||
{
|
||||
if(_importExport->getTOTPEnabled() && !doc["totp"].isNull())
|
||||
if(timeSynced && _importExport->getTOTPEnabled() && !doc["totp"].isNull())
|
||||
{
|
||||
String jsonTotp = doc["totp"];
|
||||
|
||||
@@ -1109,16 +1119,20 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
|
||||
publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_action, "--", true);
|
||||
return;
|
||||
}
|
||||
else if (_importExport->startDuoAuth((char*)"Approve Nuki Hub setting change"))
|
||||
else
|
||||
{
|
||||
bool duoRes = _importExport->startDuoAuth((char*)"Approve Nuki Hub setting change");
|
||||
int duoResult = 2;
|
||||
|
||||
if (duoRes)
|
||||
{
|
||||
while (duoResult == 2)
|
||||
{
|
||||
duoResult = _importExport->checkDuoApprove();
|
||||
delay(2000);
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
}
|
||||
|
||||
if (duoResult != 1)
|
||||
{
|
||||
|
||||
@@ -108,7 +108,7 @@ private:
|
||||
int _retryConfigCount = 0;
|
||||
int _retryLockstateCount = 0;
|
||||
int64_t _nextRetryTs = 0;
|
||||
int64_t _invalidCount = 0;
|
||||
int _invalidCount = 0;
|
||||
int64_t _lastCodeCheck = 0;
|
||||
std::vector<uint16_t> _keypadCodeIds;
|
||||
std::vector<uint32_t> _keypadCodes;
|
||||
|
||||
@@ -108,7 +108,7 @@ private:
|
||||
bool _publishAuthData = false;
|
||||
bool _clearAuthData = false;
|
||||
bool _checkKeypadCodes = false;
|
||||
int64_t _invalidCount = 0;
|
||||
int _invalidCount = 0;
|
||||
int64_t _lastCodeCheck = 0;
|
||||
std::vector<uint16_t> _keypadCodeIds;
|
||||
std::vector<uint32_t> _keypadCodes;
|
||||
|
||||
@@ -96,6 +96,8 @@
|
||||
#define preference_publish_config (char*)"nhPubConfig"
|
||||
#define preference_config_from_mqtt (char*)"nhCntrlEnabled"
|
||||
#define preference_totp_secret (char*)"totpsecret"
|
||||
#define preference_bypass_secret (char*)"bypassecret"
|
||||
#define preference_admin_secret (char*)"adminsecret"
|
||||
|
||||
// CHANGE DOES NOT REQUIRE REBOOT TO TAKE EFFECT
|
||||
#define preference_find_best_rssi (char*)"nwbestrssi"
|
||||
@@ -535,12 +537,14 @@ private:
|
||||
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_bypass_boot_btn_enabled, preference_cred_bypass_gpio_high, preference_cred_bypass_gpio_low, preference_publish_config,
|
||||
preference_config_from_mqtt, preference_totp_secret, preference_cred_session_lifetime_totp, preference_cred_session_lifetime_totp_remember
|
||||
preference_config_from_mqtt, preference_totp_secret, preference_cred_session_lifetime_totp, preference_cred_session_lifetime_totp_remember, preference_bypass_secret,
|
||||
preference_admin_secret
|
||||
};
|
||||
std::vector<char*> _redact =
|
||||
{
|
||||
preference_mqtt_user, preference_mqtt_password, preference_cred_user, preference_cred_password, preference_nuki_id_lock, preference_nuki_id_opener, preference_wifi_pass,
|
||||
preference_lock_gemini_pin, preference_cred_duo_host, preference_cred_duo_ikey, preference_cred_duo_skey, preference_cred_duo_user, preference_https_fqdn, preference_bypass_proxy
|
||||
preference_lock_gemini_pin, preference_cred_duo_host, preference_cred_duo_ikey, preference_cred_duo_skey, preference_cred_duo_user, preference_bypass_proxy,
|
||||
preference_totp_secret, preference_bypass_secret, preference_admin_secret
|
||||
};
|
||||
std::vector<char*> _boolPrefs =
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "esp_random.h"
|
||||
#ifdef CONFIG_SOC_SPIRAM_SUPPORTED
|
||||
#include "esp_psram.h"
|
||||
#include "util/SSLCert.hpp"
|
||||
#endif
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||
#include <esp_wifi.h>
|
||||
@@ -102,18 +103,22 @@ bool WebCfgServer::isAuthenticated(PsychicRequest *request, int type)
|
||||
{
|
||||
cookieKey = "totpId";
|
||||
}
|
||||
else if (type == 3)
|
||||
{
|
||||
cookieKey = "bypassId";
|
||||
}
|
||||
|
||||
if (request->hasCookie(cookieKey.c_str()))
|
||||
{
|
||||
String cookie = request->getCookie(cookieKey.c_str());
|
||||
|
||||
if ((type == 0 && _httpSessions[cookie].is<JsonVariant>()) || (type == 1 && _importExport->_duoSessions[cookie].is<JsonVariant>()) || (type == 2 && _importExport->_totpSessions[cookie].is<JsonVariant>()))
|
||||
if ((type == 0 && _httpSessions[cookie].is<JsonVariant>()) || (type == 1 && _importExport->_duoSessions[cookie].is<JsonVariant>()) || (type == 2 && _importExport->_totpSessions[cookie].is<JsonVariant>()) || (type == 3 && _importExport->_bypassSessions[cookie].is<JsonVariant>()))
|
||||
{
|
||||
struct timeval time;
|
||||
gettimeofday(&time, NULL);
|
||||
int64_t time_us = (int64_t)time.tv_sec * 1000000L + (int64_t)time.tv_usec;
|
||||
|
||||
if ((type == 0 && _httpSessions[cookie].as<signed long long>() > time_us) || (type == 1 && _importExport->_duoSessions[cookie].as<signed long long>() > time_us) || (type == 2 && _importExport->_totpSessions[cookie].as<signed long long>() > time_us))
|
||||
if ((type == 0 && _httpSessions[cookie].as<signed long long>() > time_us) || (type == 1 && _importExport->_duoSessions[cookie].as<signed long long>() > time_us) || (type == 2 && _importExport->_totpSessions[cookie].as<signed long long>() > time_us) || (type == 3 && _importExport->_bypassSessions[cookie].as<signed long long>() > time_us))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -194,6 +199,23 @@ esp_err_t WebCfgServer::logoutSession(PsychicRequest *request, PsychicResponse*
|
||||
}
|
||||
}
|
||||
|
||||
if (_importExport->getBypassEnabled())
|
||||
{
|
||||
if (!_isSSL)
|
||||
{
|
||||
resp->setCookie("bypassId", "", 0, "HttpOnly");
|
||||
}
|
||||
else
|
||||
{
|
||||
resp->setCookie("bypassId", "", 0, "Secure; HttpOnly");
|
||||
}
|
||||
|
||||
if (request->hasCookie("bypassId")) {
|
||||
String cookie2 = request->getCookie("bypassId");
|
||||
_importExport->_bypassSessions.remove(cookie2);
|
||||
}
|
||||
}
|
||||
|
||||
return buildConfirmHtml(request, resp, "Logging out", 3, true);
|
||||
}
|
||||
|
||||
@@ -369,6 +391,11 @@ int WebCfgServer::doAuthentication(PsychicRequest *request)
|
||||
_importExport->_sessionsOpts[request->client()->localIP().toString() + "totp"] = true;
|
||||
return 4;
|
||||
}
|
||||
else if(!timeSynced && _importExport->getBypassEnabled() && isAuthenticated(request, 3))
|
||||
{
|
||||
_importExport->_sessionsOpts[request->client()->localIP().toString() + "totp"] = false;
|
||||
return 4;
|
||||
}
|
||||
|
||||
Log->println("Authentication Failed");
|
||||
|
||||
@@ -505,10 +532,38 @@ void WebCfgServer::initialize()
|
||||
value = p->value();
|
||||
}
|
||||
}
|
||||
|
||||
bool adminKeyValid = false;
|
||||
if(value == "export" && timeSynced && request->hasParam("adminkey") && request->hasParam("totpkey") && _importExport->getTOTPEnabled())
|
||||
{
|
||||
String value2 = "";
|
||||
if(request->hasParam("adminkey"))
|
||||
{
|
||||
const PsychicWebParameter* p = request->getParam("adminkey");
|
||||
if(p->value() != "")
|
||||
{
|
||||
value2 = p->value();
|
||||
}
|
||||
}
|
||||
String value3 = "";
|
||||
if(request->hasParam("totpkey"))
|
||||
{
|
||||
const PsychicWebParameter* p = request->getParam("totpkey");
|
||||
if(p->value() != "")
|
||||
{
|
||||
value3 = p->value();
|
||||
}
|
||||
}
|
||||
if (value2.length() > 0 && value2 == _preferences->getString(preference_admin_secret, "") && _importExport->checkTOTP(&value3))
|
||||
{
|
||||
adminKeyValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!adminKeyValid && value != "status" && value != "login" && value != "duocheck" && value != "bypass")
|
||||
{
|
||||
int authReq = doAuthentication(request);
|
||||
|
||||
if (value != "status" && value != "login" && value != "duocheck")
|
||||
{
|
||||
switch (authReq)
|
||||
{
|
||||
case 0:
|
||||
@@ -543,14 +598,16 @@ void WebCfgServer::initialize()
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (value == "status" && authReq != 4)
|
||||
else if (value == "status")
|
||||
{
|
||||
if (doAuthentication(request) != 4)
|
||||
{
|
||||
resp->setCode(200);
|
||||
resp->setContentType("application/json");
|
||||
resp->setContent("{}");
|
||||
return resp->send();
|
||||
}
|
||||
|
||||
}
|
||||
if (value == "login")
|
||||
{
|
||||
return buildLoginHtml(request, resp);
|
||||
@@ -559,6 +616,15 @@ void WebCfgServer::initialize()
|
||||
{
|
||||
return buildTOTPHtml(request, resp, 0);
|
||||
}
|
||||
else if (value == "bypass")
|
||||
{
|
||||
return buildBypassHtml(request, resp);
|
||||
}
|
||||
else if (value == "newbypass" && _newBypass)
|
||||
{
|
||||
_newBypass = false;
|
||||
return buildConfirmHtml(request, resp, "Logged in using Bypass. New bypass: " + _preferences->getString(preference_bypass_secret, "") + " <br/><br/><a href=\"/\">Home page</a>", 3, false);
|
||||
}
|
||||
else if (value == "logout")
|
||||
{
|
||||
return logoutSession(request, resp);
|
||||
@@ -624,12 +690,17 @@ void WebCfgServer::initialize()
|
||||
return sendSettings(request, resp);
|
||||
}
|
||||
|
||||
if(adminKeyValid)
|
||||
{
|
||||
return sendSettings(request, resp, true);
|
||||
}
|
||||
|
||||
if(_importExport->_sessionsOpts[request->client()->localIP().toString() + "approve"])
|
||||
{
|
||||
_importExport->_sessionsOpts[request->client()->localIP().toString() + "approve"] = false;
|
||||
return sendSettings(request, resp);
|
||||
}
|
||||
else if(request->hasParam("totpkey") && _importExport->getTOTPEnabled())
|
||||
else if(timeSynced && request->hasParam("totpkey") && _importExport->getTOTPEnabled())
|
||||
{
|
||||
const PsychicWebParameter* pass = request->getParam("totpkey");
|
||||
if(pass->value() != "")
|
||||
@@ -704,6 +775,10 @@ void WebCfgServer::initialize()
|
||||
{
|
||||
return buildHttpSSLConfigHtml(request, resp, 2);
|
||||
}
|
||||
else if (value == "selfsignhttps")
|
||||
{
|
||||
return buildHttpSSLConfigHtml(request, resp, 3);
|
||||
}
|
||||
else if (value == "nukicfg")
|
||||
{
|
||||
return buildNukiConfigHtml(request, resp);
|
||||
@@ -816,7 +891,33 @@ void WebCfgServer::initialize()
|
||||
}
|
||||
}
|
||||
|
||||
if (value != "login" && value != "totp")
|
||||
bool adminKeyValid = false;
|
||||
if(value == "import" && timeSynced && request->hasParam("adminkey") && request->hasParam("totpkey") && _importExport->getTOTPEnabled())
|
||||
{
|
||||
String value2 = "";
|
||||
if(request->hasParam("adminkey"))
|
||||
{
|
||||
const PsychicWebParameter* p = request->getParam("adminkey");
|
||||
if(p->value() != "")
|
||||
{
|
||||
value2 = p->value();
|
||||
}
|
||||
}
|
||||
String value3 = "";
|
||||
if(request->hasParam("totpkey"))
|
||||
{
|
||||
const PsychicWebParameter* p = request->getParam("totpkey");
|
||||
if(p->value() != "")
|
||||
{
|
||||
value3 = p->value();
|
||||
}
|
||||
}
|
||||
if (value2.length() > 0 && value2 == _preferences->getString(preference_admin_secret, "") && _importExport->checkTOTP(&value3))
|
||||
{
|
||||
adminKeyValid = true;
|
||||
}
|
||||
}
|
||||
if(!adminKeyValid && value != "login" && value != "totp" && value != "bypass")
|
||||
{
|
||||
int authReq = doAuthentication(request);
|
||||
|
||||
@@ -853,7 +954,7 @@ void WebCfgServer::initialize()
|
||||
if(!_importExport->_sessionsOpts[request->client()->localIP().toString() + "approve"])
|
||||
{
|
||||
bool approved = false;
|
||||
if(request->hasParam("totpkey") && _importExport->getTOTPEnabled())
|
||||
if(timeSynced && request->hasParam("totpkey") && _importExport->getTOTPEnabled())
|
||||
{
|
||||
const PsychicWebParameter* pass = request->getParam("totpkey");
|
||||
if(pass->value() != "")
|
||||
@@ -866,6 +967,11 @@ void WebCfgServer::initialize()
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(!timeSynced && _importExport->getBypassEnabled() && isAuthenticated(request, 3))
|
||||
{
|
||||
_importExport->_sessionsOpts[request->client()->localIP().toString() + "approve"] = false;
|
||||
approved = true;
|
||||
}
|
||||
|
||||
if (!approved)
|
||||
{
|
||||
@@ -926,6 +1032,23 @@ void WebCfgServer::initialize()
|
||||
return resp->redirect("/get?page=totp");
|
||||
}
|
||||
}
|
||||
else if (value == "bypass")
|
||||
{
|
||||
bool loggedIn = processBypass(request, resp);
|
||||
if (loggedIn)
|
||||
{
|
||||
resp->setCode(302);
|
||||
resp->addHeader("Cache-Control", "no-cache");
|
||||
_newBypass = true;
|
||||
return resp->redirect("/get?page=newbypass");
|
||||
}
|
||||
else
|
||||
{
|
||||
resp->setCode(302);
|
||||
resp->addHeader("Cache-Control", "no-cache");
|
||||
return resp->redirect("/");
|
||||
}
|
||||
}
|
||||
#ifndef NUKI_HUB_UPDATER
|
||||
else if (value == "savecfg")
|
||||
{
|
||||
@@ -969,8 +1092,24 @@ void WebCfgServer::initialize()
|
||||
{
|
||||
String message = "";
|
||||
bool restart = processImport(request, resp, message);
|
||||
|
||||
if(adminKeyValid)
|
||||
{
|
||||
resp->setCode(200);
|
||||
resp->setContentType("application/json");
|
||||
resp->setContent("{ \"result\": \"success\"}");
|
||||
esp_err_t res = resp->send();
|
||||
if(restart)
|
||||
{
|
||||
restartEsp(RestartReason::RequestedViaWebServer);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
else
|
||||
{
|
||||
return buildConfirmHtml(request, resp, message, 3, true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
@@ -1880,18 +2019,20 @@ esp_err_t WebCfgServer::buildLoginHtml(PsychicRequest *request, PsychicResponse*
|
||||
|
||||
esp_err_t WebCfgServer::buildTOTPHtml(PsychicRequest *request, PsychicResponse* resp, int type)
|
||||
{
|
||||
if (!timeSynced)
|
||||
{
|
||||
return buildConfirmHtml(request, resp, "NTP time not synced yet, TOTP not available, please wait for NTP to sync or use <a href=\"/get?page=bypass\">one-time bypass</a>", 3, true);
|
||||
}
|
||||
|
||||
if((pow(_importExport->_invalidCount, 5) + _importExport->_lastCodeCheck) > espMillis())
|
||||
{
|
||||
return buildConfirmHtml(request, resp, "Too many invalid TOTP tries, please wait before retrying or use <a href=\"/get?page=bypass\">one-time bypass</a>", 3, true);
|
||||
}
|
||||
|
||||
PsychicStreamResponse response(resp, "text/html");
|
||||
response.beginSend();
|
||||
response.print("<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
|
||||
response.print("<style>form{border:3px solid #f1f1f1; max-width: 400px;}input[type=password],input[type=text]{width:100%;padding:12px 20px;margin:8px 0;display:inline-block;border:1px solid #ccc;box-sizing:border-box}button{background-color:#04aa6d;color:#fff;padding:14px 20px;margin:8px 0;border:none;cursor:pointer;width:100%}button:hover{opacity:.8}.container{padding:16px}span.password{float:right;padding-top:16px}@media screen and (max-width:300px){span.psw{display:block;float:none}}</style>");
|
||||
/*
|
||||
if (!timeSynced)
|
||||
{
|
||||
char millis[20];
|
||||
itoa(espMillis(), millis, 10);
|
||||
response.print((String)"<script>window.onload = function() { var startTime = Date.now(); var interval = setInterval(function() { var elapsedTime = Date.now() - startTime; document.getElementById(\"timestamp\").innerHTML = (elapsedTime / 1000).toFixed(3) + " + millis + ";}, 100); }</script>");
|
||||
}
|
||||
*/
|
||||
response.print("</head><body><center><h2>Nuki Hub TOTP</h2>");
|
||||
|
||||
String typeText = "Login";
|
||||
@@ -1931,12 +2072,6 @@ esp_err_t WebCfgServer::buildTOTPHtml(PsychicRequest *request, PsychicResponse*
|
||||
|
||||
response.print("<div class=\"container\">");
|
||||
response.print("<label for=\"totpkey\"><b>TOTP</b></label><input type=\"text\" placeholder=\"Enter TOTP code\" name=\"totpkey\">");
|
||||
/*
|
||||
if (!timeSynced)
|
||||
{
|
||||
response.print("<label for=\"timestamp\"><b>Timestamp</b></label><span type=\"text\" id=\"timestamp\"></span>");
|
||||
}
|
||||
*/
|
||||
response.print("<button type=\"submit\" ");
|
||||
if(type == 1)
|
||||
{
|
||||
@@ -1947,6 +2082,32 @@ esp_err_t WebCfgServer::buildTOTPHtml(PsychicRequest *request, PsychicResponse*
|
||||
return response.endSend();
|
||||
}
|
||||
|
||||
esp_err_t WebCfgServer::buildBypassHtml(PsychicRequest *request, PsychicResponse* resp)
|
||||
{
|
||||
if (timeSynced)
|
||||
{
|
||||
return buildConfirmHtml(request, resp, "One-time bypass is only available if NTP time is not synced</a>", 3, true);
|
||||
}
|
||||
|
||||
if((pow(_importExport->_invalidCount2, 5) + _importExport->_lastCodeCheck2) > espMillis())
|
||||
{
|
||||
return buildConfirmHtml(request, resp, "Too many invalid bypass tries, please wait before retrying", 3, true);
|
||||
}
|
||||
|
||||
PsychicStreamResponse response(resp, "text/html");
|
||||
response.beginSend();
|
||||
response.print("<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
|
||||
response.print("<style>form{border:3px solid #f1f1f1; max-width: 400px;}input[type=password],input[type=text]{width:100%;padding:12px 20px;margin:8px 0;display:inline-block;border:1px solid #ccc;box-sizing:border-box}button{background-color:#04aa6d;color:#fff;padding:14px 20px;margin:8px 0;border:none;cursor:pointer;width:100%}button:hover{opacity:.8}.container{padding:16px}span.password{float:right;padding-top:16px}@media screen and (max-width:300px){span.psw{display:block;float:none}}</style>");
|
||||
response.print("</head><body><center><h2>Nuki Hub One-time Bypass</h2>");
|
||||
response.print("<form action=\"/post?page=bypass\" method=\"post\">");
|
||||
response.print("<div class=\"container\">");
|
||||
response.print("<label for=\"bypass\"><b>Bypass code</b></label><input type=\"text\" placeholder=\"Enter bypass code\" name=\"bypass\">");
|
||||
response.print("<button type=\"submit\" ");
|
||||
response.print(">Login</button></div>");
|
||||
response.print("</form></center></body></html>");
|
||||
return response.endSend();
|
||||
}
|
||||
|
||||
esp_err_t WebCfgServer::buildDuoCheckHtml(PsychicRequest *request, PsychicResponse* resp)
|
||||
{
|
||||
char valueStr[2];
|
||||
@@ -1990,7 +2151,7 @@ esp_err_t WebCfgServer::buildDuoHtml(PsychicRequest *request, PsychicResponse* r
|
||||
{
|
||||
if (!timeSynced)
|
||||
{
|
||||
return buildConfirmHtml(request, resp, "NTP time not synced yet, Duo not available, please wait for NTP to sync", 3, true);
|
||||
return buildConfirmHtml(request, resp, "NTP time not synced yet, Duo not available, please wait for NTP to sync or use <a href=\"/get?page=bypass\">one-time bypass</a>", 3, true);
|
||||
}
|
||||
|
||||
String duoText;
|
||||
@@ -2134,9 +2295,55 @@ bool WebCfgServer::processLogin(PsychicRequest *request, PsychicResponse* resp)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WebCfgServer::processBypass(PsychicRequest *request, PsychicResponse* resp)
|
||||
{
|
||||
if(!timeSynced && request->hasParam("bypass"))
|
||||
{
|
||||
const PsychicWebParameter* pass = request->getParam("bypass");
|
||||
if(pass->value() != "")
|
||||
{
|
||||
String bypass = pass->value();
|
||||
if (_importExport->checkBypass(bypass))
|
||||
{
|
||||
char buffer[33];
|
||||
int i;
|
||||
for (i = 0; i < 4; i++) {
|
||||
sprintf(buffer + (i * 8), "%08lx", (unsigned long int)esp_random());
|
||||
}
|
||||
|
||||
if (!_isSSL)
|
||||
{
|
||||
resp->setCookie("bypassId", buffer, 3600, "HttpOnly");
|
||||
}
|
||||
else
|
||||
{
|
||||
resp->setCookie("bypassId", buffer, 3600, "Secure; HttpOnly");
|
||||
}
|
||||
|
||||
struct timeval time;
|
||||
gettimeofday(&time, NULL);
|
||||
int64_t time_us = (int64_t)time.tv_sec * 1000000L + (int64_t)time.tv_usec;
|
||||
_importExport->_bypassSessions[buffer] = time_us + ((int64_t)3600*1000000L);
|
||||
|
||||
char randomstr2[33];
|
||||
randomSeed(analogRead(0));
|
||||
char chars[] = {'1', '2', '3','4', '5', '6','7', '8', '9', '0', 'A', 'B', 'C', 'D','E', 'F', 'G','H', 'I', 'J','K', 'L', 'M', 'N', 'O','P', 'Q','R', 'S', 'T','U', 'V', 'W','X', 'Y', 'Z'};
|
||||
for(int i = 0;i < 32; i++){
|
||||
randomstr2[i] = chars[random(36)];
|
||||
}
|
||||
randomstr2[32] = '\0';
|
||||
_preferences->putString(preference_bypass_secret, randomstr2);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WebCfgServer::processTOTP(PsychicRequest *request, PsychicResponse* resp)
|
||||
{
|
||||
if(request->hasParam("totpkey"))
|
||||
if(timeSynced && request->hasParam("totpkey"))
|
||||
{
|
||||
const PsychicWebParameter* pass = request->getParam("totpkey");
|
||||
if(pass->value() != "")
|
||||
@@ -2178,7 +2385,7 @@ bool WebCfgServer::processTOTP(PsychicRequest *request, PsychicResponse* resp)
|
||||
}
|
||||
|
||||
#ifndef NUKI_HUB_UPDATER
|
||||
esp_err_t WebCfgServer::sendSettings(PsychicRequest *request, PsychicResponse* resp)
|
||||
esp_err_t WebCfgServer::sendSettings(PsychicRequest *request, PsychicResponse* resp, bool adminKey)
|
||||
{
|
||||
JsonDocument json;
|
||||
String jsonPretty;
|
||||
@@ -2227,7 +2434,10 @@ esp_err_t WebCfgServer::sendSettings(PsychicRequest *request, PsychicResponse* r
|
||||
serializeJsonPretty(json, jsonPretty);
|
||||
char buf[26 + name.length()];
|
||||
snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", name.c_str());
|
||||
if(!adminKey)
|
||||
{
|
||||
resp->addHeader("Content-Disposition", buf);
|
||||
}
|
||||
resp->setCode(200);
|
||||
resp->setContentType("application/json");
|
||||
resp->setContent(jsonPretty.c_str());
|
||||
@@ -2522,6 +2732,13 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(key == "HTTPGEN")
|
||||
{
|
||||
createSSLCertificate();
|
||||
Log->print("Setting changed: ");
|
||||
Log->println(key);
|
||||
configChanged = true;
|
||||
}
|
||||
#endif
|
||||
else if(key == "UPTIME")
|
||||
{
|
||||
@@ -4155,6 +4372,32 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(key == "CREDBYPASS")
|
||||
{
|
||||
if(value != "*")
|
||||
{
|
||||
if(_preferences->getString(preference_bypass_secret, "") != value)
|
||||
{
|
||||
_preferences->putString(preference_bypass_secret, value);
|
||||
Log->print("Setting changed: ");
|
||||
Log->println(key);
|
||||
configChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(key == "CREDADMIN")
|
||||
{
|
||||
if(value != "*")
|
||||
{
|
||||
if(_preferences->getString(preference_admin_secret, "") != value)
|
||||
{
|
||||
_preferences->putString(preference_admin_secret, value);
|
||||
Log->print("Setting changed: ");
|
||||
Log->println(key);
|
||||
configChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(key == "NUKIPIN" && _nuki != nullptr)
|
||||
{
|
||||
if(value == "#")
|
||||
@@ -4705,13 +4948,27 @@ esp_err_t WebCfgServer::buildHtml(PsychicRequest *request, PsychicResponse* resp
|
||||
|
||||
esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request, PsychicResponse* resp)
|
||||
{
|
||||
char chars[] = {'2', '3','4', '5', '6','7', 'A', 'B', 'C', 'D','E', 'F', 'G','H', 'I', 'J','K', 'L', 'M', 'N', 'O','P', 'Q','R', 'S', 'T','U', 'V', 'W','X', 'Y', 'Z'};
|
||||
char chars2[] = {'1', '2', '3','4', '5', '6','7', '8', '9', '0', 'A', 'B', 'C', 'D','E', 'F', 'G','H', 'I', 'J','K', 'L', 'M', 'N', 'O','P', 'Q','R', 'S', 'T','U', 'V', 'W','X', 'Y', 'Z'};
|
||||
|
||||
char randomstr[17];
|
||||
randomSeed(analogRead(0));
|
||||
char chars[] = {'2', '3','4', '5', '6','7', 'A', 'B', 'C', 'D','E', 'F', 'G','H', 'I', 'J','K', 'L', 'M', 'N', 'O','P', 'Q','R', 'S', 'T','U', 'V', 'W','X', 'Y', 'Z'};
|
||||
for(int i = 0;i < 16; i++){
|
||||
randomstr[i] = chars[random(32)];
|
||||
}
|
||||
randomstr[16] = '\0';
|
||||
char randomstr2[33];
|
||||
randomSeed(analogRead(0));
|
||||
for(int i = 0;i < 32; i++){
|
||||
randomstr2[i] = chars2[random(36)];
|
||||
}
|
||||
randomstr2[32] = '\0';
|
||||
char randomstr3[33];
|
||||
randomSeed(analogRead(0));
|
||||
for(int i = 0;i < 32; i++){
|
||||
randomstr3[i] = chars2[random(36)];
|
||||
}
|
||||
randomstr3[32] = '\0';
|
||||
|
||||
PsychicStreamResponse response(resp, "text/html");
|
||||
response.beginSend();
|
||||
@@ -4738,10 +4995,18 @@ esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request, PsychicResponse*
|
||||
printInputField(&response, "DUOIKEY", "Duo integration key", "*", 255, "", true, false);
|
||||
printInputField(&response, "DUOSKEY", "Duo secret key", "*", 255, "", true, false);
|
||||
printInputField(&response, "DUOUSER", "Duo user", "*", 255, "", true, false);
|
||||
printInputField(&response, "CREDTOTP", "TOTP Secret Key (requires Form authentication)", "*", 16, "", true, false);
|
||||
printInputField(&response, "CREDTOTP", "TOTP Secret Key", "*", 16, "", true, false);
|
||||
response.print("<tr id=\"totpgentr\" ><td><input type=\"button\" id=\"totpgen\" onclick=\"document.getElementsByName('CREDTOTP')[0].type='text'; document.getElementsByName('CREDTOTP')[0].value='");
|
||||
response.print(randomstr);
|
||||
response.print("'; document.getElementById('totpgentr').style.display='none';\" value=\"Generate new TOTP key\"></td></tr>");
|
||||
printInputField(&response, "CREDBYPASS", "One-time MFA Bypass", "*", 32, "", true, false);
|
||||
response.print("<tr id=\"bypassgentr\" ><td><input type=\"button\" id=\"bypassgen\" onclick=\"document.getElementsByName('CREDBYPASS')[0].type='text'; document.getElementsByName('CREDBYPASS')[0].value='");
|
||||
response.print(randomstr2);
|
||||
response.print("'; document.getElementById('bypassgentr').style.display='none';\" value=\"Generate new Bypass\"></td></tr>");
|
||||
printInputField(&response, "CREDADMIN", "Admin key", "*", 32, "", true, false);
|
||||
response.print("<tr id=\"admingentr\" ><td><input type=\"button\" id=\"admingen\" onclick=\"document.getElementsByName('CREDADMIN')[0].type='text'; document.getElementsByName('CREDADMIN')[0].value='");
|
||||
response.print(randomstr3);
|
||||
response.print("'; document.getElementById('admingentr').style.display='none';\" value=\"Generate new Admin key\"></td></tr>");
|
||||
printInputField(&response, "CREDLFTM", "Session validity (in seconds)", _preferences->getInt(preference_cred_session_lifetime, 3600), 12, "");
|
||||
printInputField(&response, "CREDLFTMRMBR", "Session validity remember (in hours)", _preferences->getInt(preference_cred_session_lifetime_remember, 720), 12, "");
|
||||
printInputField(&response, "CREDDUOLFTM", "Duo Session validity (in seconds)", _preferences->getInt(preference_cred_session_lifetime_duo, 3600), 12, "");
|
||||
@@ -4844,6 +5109,7 @@ esp_err_t WebCfgServer::buildNetworkConfigHtml(PsychicRequest *request, PsychicR
|
||||
{
|
||||
response.print("<tr><td>Set HTTP SSL Certificate</td><td><button title=\"Set HTTP SSL Certificate\" onclick=\" window.open('/get?page=httpcrtconfig', '_self'); return false;\">Change</button></td></tr>");
|
||||
response.print("<tr><td>Set HTTP SSL Key</td><td><button title=\"Set HTTP SSL Key\" onclick=\" window.open('/get?page=httpkeyconfig', '_self'); return false;\">Change</button></td></tr>");
|
||||
response.print("<tr><td>Generate self-signed HTTP SSL Certificate and key</td><td><button title=\"Generate HTTP SSL Certificate and key\" onclick=\" window.open('/get?page=selfsignhttps', '_self'); return false;\">Generate</button></td></tr>");
|
||||
printInputField(&response, "HTTPSFQDN", "Nuki Hub FQDN for HTTP redirect", _preferences->getString(preference_https_fqdn, "").c_str(), 255, "");
|
||||
}
|
||||
#endif
|
||||
@@ -5073,7 +5339,7 @@ esp_err_t WebCfgServer::buildHttpSSLConfigHtml(PsychicRequest *request, PsychicR
|
||||
printTextarea(&response, "HTTPCRT", "HTTP SSL Certificate (*, optional)", "", 4400, true, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (type == 2)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
@@ -5107,6 +5373,11 @@ esp_err_t WebCfgServer::buildHttpSSLConfigHtml(PsychicRequest *request, PsychicR
|
||||
printTextarea(&response, "HTTPKEY", "HTTP SSL Key (*, optional)", "", 2200, true, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
response.print("<input type=\"hidden\" name=\"HTTPGEN\" value=\"1\">");
|
||||
response.print("<tr><td>Click save to generate a HTTPS SSL Certificate and key</td></tr>");
|
||||
}
|
||||
response.print("</table>");
|
||||
response.print("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
|
||||
response.print("</form>");
|
||||
@@ -5677,6 +5948,11 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request, PsychicResponse*
|
||||
response.print(uxTaskGetStackHighWaterMark(networkTaskHandle));
|
||||
response.print("\nNuki task stack high watermark: ");
|
||||
response.print(uxTaskGetStackHighWaterMark(nukiTaskHandle));
|
||||
SPIFFS.begin(true);
|
||||
response.print("\n\n------------ SPIFFS ------------");
|
||||
response.printf("\nSPIFFS Total Bytes: %u", SPIFFS.totalBytes());
|
||||
response.printf("\nSPIFFS Used Bytes: %u", SPIFFS.usedBytes());
|
||||
response.printf("\nSPIFFS Free Bytes: %u", SPIFFS.totalBytes() - SPIFFS.usedBytes());
|
||||
response.print("\n\n------------ GENERAL SETTINGS ------------");
|
||||
response.print("\nNetwork task stack size: ");
|
||||
response.print(_preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE));
|
||||
@@ -6752,4 +7028,59 @@ const String WebCfgServer::getPreselectionForGpio(const uint8_t &pin) const
|
||||
|
||||
return String((int8_t)PinRole::Disabled);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SOC_SPIRAM_SUPPORTED
|
||||
void WebCfgServer::createSSLCertificate()
|
||||
{
|
||||
SSLCert* cert;
|
||||
cert = new SSLCert();
|
||||
int createCertResult = createSelfSignedCert(
|
||||
*cert,
|
||||
KEYSIZE_2048,
|
||||
"CN=nukihub.local,O=NukiHub,C=DE",
|
||||
"20250101000000",
|
||||
"20350101000000"
|
||||
);
|
||||
|
||||
if (createCertResult == 0) {
|
||||
if (!SPIFFS.begin(true)) {
|
||||
Log->println("SPIFFS Mount Failed");
|
||||
}
|
||||
else
|
||||
{
|
||||
File file = SPIFFS.open("/http_ssl.crt", FILE_WRITE);
|
||||
if (!file) {
|
||||
Log->println("Failed to open /http_ssl.crt for writing");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!file.print(cert->getCertPEM()))
|
||||
{
|
||||
Log->println("Failed to write /http_ssl.crt");
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
File file2 = SPIFFS.open("/http_ssl.key", FILE_WRITE);
|
||||
if (!file2) {
|
||||
Log->println("Failed to open /http_ssl.key for writing");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!file2.print(cert->getKeyPEM()))
|
||||
{
|
||||
Log->println("Failed to write /http_ssl.key");
|
||||
}
|
||||
file2.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log->print("SSL Self sign failed: ");
|
||||
Log->println(createCertResult);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
|
||||
private:
|
||||
#ifndef NUKI_HUB_UPDATER
|
||||
esp_err_t sendSettings(PsychicRequest *request, PsychicResponse* resp);
|
||||
esp_err_t sendSettings(PsychicRequest *request, PsychicResponse* resp, bool adminKey = false);
|
||||
bool processArgs(PsychicRequest *request, PsychicResponse* resp, String& message);
|
||||
bool processImport(PsychicRequest *request, PsychicResponse* resp, String& message);
|
||||
void processGpioArgs(PsychicRequest *request, PsychicResponse* resp);
|
||||
@@ -86,7 +86,9 @@ private:
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
||||
const std::vector<std::pair<String, String>> getNetworkCustomCLKOptions() const;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SOC_SPIRAM_SUPPORTED
|
||||
void createSSLCertificate();
|
||||
#endif
|
||||
const String getPreselectionForGpio(const uint8_t& pin) const;
|
||||
const String pinStateToString(const NukiPinState& value) const;
|
||||
|
||||
@@ -111,9 +113,11 @@ private:
|
||||
bool isAuthenticated(PsychicRequest *request, int type = 0);
|
||||
bool processLogin(PsychicRequest *request, PsychicResponse* resp);
|
||||
bool processTOTP(PsychicRequest *request, PsychicResponse* resp);
|
||||
bool processBypass(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 buildBypassHtml(PsychicRequest *request, PsychicResponse* resp);
|
||||
esp_err_t buildTOTPHtml(PsychicRequest *request, PsychicResponse* resp, int type);
|
||||
esp_err_t buildDuoHtml(PsychicRequest *request, PsychicResponse* resp, int type);
|
||||
esp_err_t buildDuoCheckHtml(PsychicRequest *request, PsychicResponse* resp);
|
||||
@@ -150,6 +154,7 @@ private:
|
||||
JsonDocument _httpSessions;
|
||||
bool _duoEnabled = false;
|
||||
bool _bypassGPIO = false;
|
||||
bool _newBypass = false;
|
||||
int _bypassGPIOHigh = -1;
|
||||
int _bypassGPIOLow = -1;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ dependencies:
|
||||
|
||||
esp-nimble-cpp:
|
||||
git: https://github.com/h2zero/esp-nimble-cpp.git
|
||||
version: fa468d360a56712f3f39a1fba74b97340ebca8a9
|
||||
version: 74b5c59887a381ca02c8193384bd89968c8409a6
|
||||
|
||||
espressif/libsodium: "^1.0.20~2"
|
||||
|
||||
|
||||
51
src/main.cpp
51
src/main.cpp
@@ -185,6 +185,43 @@ uint8_t checkPartition()
|
||||
}
|
||||
}
|
||||
|
||||
void listDir(fs::FS &fs, const char *dirname, uint8_t levels) {
|
||||
Serial.printf("Listing directory: %s\r\n", dirname);
|
||||
|
||||
File root = fs.open(dirname);
|
||||
if (!root) {
|
||||
Serial.println("- failed to open directory");
|
||||
return;
|
||||
}
|
||||
if (!root.isDirectory()) {
|
||||
Serial.println(" - not a directory");
|
||||
return;
|
||||
}
|
||||
|
||||
File file = root.openNextFile();
|
||||
while (file) {
|
||||
if (file.isDirectory()) {
|
||||
Serial.print(" DIR : ");
|
||||
Serial.println(file.name());
|
||||
if (levels) {
|
||||
listDir(fs, file.path(), levels - 1);
|
||||
}
|
||||
} else {
|
||||
Serial.print(" FILE: ");
|
||||
Serial.print(file.name());
|
||||
Serial.print("\tSIZE: ");
|
||||
Serial.println(file.size());
|
||||
}
|
||||
|
||||
if (file.size() > (int)(SPIFFS.totalBytes() * 0.4))
|
||||
{
|
||||
SPIFFS.remove((String)"/" + file.name());
|
||||
}
|
||||
|
||||
file = root.openNextFile();
|
||||
}
|
||||
}
|
||||
|
||||
void cbSyncTime(struct timeval *tv) {
|
||||
Log->println("NTP time synced");
|
||||
timeSynced = true;
|
||||
@@ -518,7 +555,7 @@ void logCoreDump()
|
||||
{
|
||||
coredumpPrinted = false;
|
||||
delay(500);
|
||||
Serial.println("Printing coredump and saving to coredump.hex on SPIFFS");
|
||||
Log->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)
|
||||
@@ -528,6 +565,7 @@ void logCoreDump()
|
||||
|
||||
if (pt != NULL)
|
||||
{
|
||||
File file;
|
||||
uint8_t bf[256];
|
||||
char str_dst[640];
|
||||
int16_t toRead;
|
||||
@@ -536,8 +574,9 @@ void logCoreDump()
|
||||
{
|
||||
Log->println("SPIFFS Mount Failed");
|
||||
}
|
||||
|
||||
File file = SPIFFS.open("/coredump.hex", FILE_WRITE);
|
||||
else
|
||||
{
|
||||
file = SPIFFS.open("/coredump.hex", FILE_WRITE);
|
||||
if (!file) {
|
||||
Log->println("Failed to open /coredump.hex for writing");
|
||||
}
|
||||
@@ -546,6 +585,7 @@ void logCoreDump()
|
||||
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);
|
||||
@@ -633,6 +673,11 @@ void setup()
|
||||
logCoreDump();
|
||||
}
|
||||
|
||||
if (SPIFFS.begin(true))
|
||||
{
|
||||
listDir(SPIFFS, "/", 1);
|
||||
}
|
||||
|
||||
uint8_t partitionType = checkPartition();
|
||||
|
||||
//default disableNetwork RTC_ATTR to false on power-on
|
||||
|
||||
397
src/util/SSLCert.cpp
Normal file
397
src/util/SSLCert.cpp
Normal file
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Frank Hessel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "SSLCert.hpp"
|
||||
|
||||
SSLCert::SSLCert(uint16_t certLength, uint16_t pkLength, String keyPEM, String certPEM):
|
||||
_certLength(certLength),
|
||||
_pkLength(pkLength),
|
||||
_keyPEM(keyPEM),
|
||||
_certPEM(certPEM) {
|
||||
|
||||
}
|
||||
|
||||
SSLCert::~SSLCert() {
|
||||
// TODO Auto-generated destructor stub
|
||||
}
|
||||
|
||||
uint16_t SSLCert::getCertLength() {
|
||||
return _certLength;
|
||||
}
|
||||
|
||||
uint16_t SSLCert::getPKLength() {
|
||||
return _pkLength;
|
||||
}
|
||||
|
||||
String SSLCert::getKeyPEM() {
|
||||
return _keyPEM;
|
||||
}
|
||||
|
||||
String SSLCert::getCertPEM() {
|
||||
return _certPEM;
|
||||
}
|
||||
|
||||
void SSLCert::setPK(String keyPEM) {
|
||||
_keyPEM = keyPEM;
|
||||
_pkLength = keyPEM.length();
|
||||
}
|
||||
|
||||
|
||||
void SSLCert::setCert(String certPEM) {
|
||||
_certPEM = certPEM;
|
||||
_certLength = certPEM.length();
|
||||
}
|
||||
|
||||
void SSLCert::clear() {
|
||||
_certLength = 0;
|
||||
_pkLength = 0;
|
||||
|
||||
_keyPEM = "";
|
||||
_certPEM = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CN value from a DN, or "" if it cannot be found
|
||||
*/
|
||||
static std::string get_cn(std::string dn) {
|
||||
size_t cnStart = dn.find("CN=");
|
||||
if (cnStart == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
cnStart += 3;
|
||||
size_t cnStop = dn.find(",", cnStart);
|
||||
if (cnStop == std::string::npos) {
|
||||
cnStop = dn.length();
|
||||
}
|
||||
return dn.substr(cnStart, cnStop - cnStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the DN as subjectAltName extension in the certificate
|
||||
*/
|
||||
static int add_subject_alt_name(mbedtls_x509write_cert *crt, std::string &cn) {
|
||||
size_t bufsize = cn.length() + 8; // some additional space for tags and length fields
|
||||
uint8_t buf[bufsize];
|
||||
uint8_t *p = &buf[bufsize - 1];
|
||||
uint8_t *start = buf;
|
||||
int length = 0;
|
||||
int ret; // used by MBEDTLS macro
|
||||
|
||||
// The ASN structure that we will construct as parameter for write_crt_set_extension is as follows:
|
||||
// | 0x30 = Sequence | length | 0x82 = dNSName, context-specific | length | cn0 | cn1 | cn2 | cn3 | .. | cnn |
|
||||
// ↑ : ↑ `-------------v------------------´:
|
||||
// | : `-------------------´ :
|
||||
// | `----------v------------------------------------------------------------------´
|
||||
// `---------------´
|
||||
// Let's encrypt has useful infos: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/#choice-and-any-encoding
|
||||
MBEDTLS_ASN1_CHK_ADD(length,
|
||||
mbedtls_asn1_write_raw_buffer(&p, start, (uint8_t*)cn.c_str(), cn.length()));
|
||||
MBEDTLS_ASN1_CHK_ADD(length,
|
||||
mbedtls_asn1_write_len(&p, start, length));
|
||||
MBEDTLS_ASN1_CHK_ADD(length,
|
||||
mbedtls_asn1_write_tag(&p, start, MBEDTLS_ASN1_CONTEXT_SPECIFIC | 0x02)); // 0x02 = dNSName
|
||||
MBEDTLS_ASN1_CHK_ADD(length,
|
||||
mbedtls_asn1_write_len(&p, start, length));
|
||||
MBEDTLS_ASN1_CHK_ADD(length,
|
||||
mbedtls_asn1_write_tag(&p, start, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE ));
|
||||
return mbedtls_x509write_crt_set_extension( crt,
|
||||
MBEDTLS_OID_SUBJECT_ALT_NAME, MBEDTLS_OID_SIZE(MBEDTLS_OID_SUBJECT_ALT_NAME),
|
||||
0, // not critical
|
||||
p, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to create the key for a self-signed certificate.
|
||||
*
|
||||
* Writes private key as DER in certCtx
|
||||
*
|
||||
* Based on programs/pkey/gen_key.c
|
||||
*/
|
||||
static int gen_key(SSLCert &certCtx, SSLKeySize keySize) {
|
||||
|
||||
// Initialize the entropy source
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_entropy_init( &entropy );
|
||||
|
||||
// Initialize the RNG
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
mbedtls_ctr_drbg_init( &ctr_drbg );
|
||||
int rngRes = mbedtls_ctr_drbg_seed(
|
||||
&ctr_drbg, mbedtls_entropy_func, &entropy,
|
||||
NULL, 0
|
||||
);
|
||||
if (rngRes != 0) {
|
||||
mbedtls_entropy_free( &entropy );
|
||||
return HTTPS_SERVER_ERROR_KEYGEN_RNG;
|
||||
}
|
||||
|
||||
// Initialize the private key
|
||||
mbedtls_pk_context key;
|
||||
mbedtls_pk_init( &key );
|
||||
int resPkSetup = mbedtls_pk_setup( &key, mbedtls_pk_info_from_type( MBEDTLS_PK_RSA ) );
|
||||
if ( resPkSetup != 0) {
|
||||
mbedtls_ctr_drbg_free( &ctr_drbg );
|
||||
mbedtls_entropy_free( &entropy );
|
||||
return HTTPS_SERVER_ERROR_KEYGEN_SETUP_PK;
|
||||
}
|
||||
|
||||
// Actual key generation
|
||||
int resPkGen = mbedtls_rsa_gen_key(
|
||||
mbedtls_pk_rsa( key ),
|
||||
mbedtls_ctr_drbg_random,
|
||||
&ctr_drbg,
|
||||
keySize,
|
||||
65537
|
||||
);
|
||||
if ( resPkGen != 0) {
|
||||
mbedtls_pk_free( &key );
|
||||
mbedtls_ctr_drbg_free( &ctr_drbg );
|
||||
mbedtls_entropy_free( &entropy );
|
||||
return HTTPS_SERVER_ERROR_KEYGEN_GEN_PK;
|
||||
}
|
||||
|
||||
// Free the entropy source and the RNG as they are no longer needed
|
||||
mbedtls_ctr_drbg_free( &ctr_drbg );
|
||||
mbedtls_entropy_free( &entropy );
|
||||
|
||||
// Allocate the space on the heap, as stack size is quite limited
|
||||
unsigned char * output_buf = new unsigned char[4096];
|
||||
if (output_buf == NULL) {
|
||||
mbedtls_pk_free( &key );
|
||||
return HTTPS_SERVER_ERROR_KEY_OUT_OF_MEM;
|
||||
}
|
||||
memset(output_buf, 0, 4096);
|
||||
|
||||
// Write the key to the temporary buffer and determine its length
|
||||
int resPkWrite = mbedtls_pk_write_key_pem( &key, output_buf, 4096 );
|
||||
if (resPkWrite < 0) {
|
||||
delete[] output_buf;
|
||||
mbedtls_pk_free( &key );
|
||||
return HTTPS_SERVER_ERROR_KEY_WRITE_PK;
|
||||
}
|
||||
|
||||
// Clean up the temporary buffer and clear the key context
|
||||
mbedtls_pk_free( &key );
|
||||
|
||||
// Set the private key in the context
|
||||
certCtx.setPK((char*)output_buf);
|
||||
|
||||
delete[] output_buf;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_serial_decimal_format(unsigned char *obuf, size_t obufmax,
|
||||
const char *ibuf, size_t *len)
|
||||
{
|
||||
unsigned long long int dec;
|
||||
unsigned int remaining_bytes = sizeof(dec);
|
||||
unsigned char *p = obuf;
|
||||
unsigned char val;
|
||||
char *end_ptr = NULL;
|
||||
|
||||
errno = 0;
|
||||
dec = strtoull(ibuf, &end_ptr, 10);
|
||||
|
||||
if ((errno != 0) || (end_ptr == ibuf)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*len = 0;
|
||||
|
||||
while (remaining_bytes > 0) {
|
||||
if (obufmax < (*len + 1)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
val = (dec >> ((remaining_bytes - 1) * 8)) & 0xFF;
|
||||
|
||||
/* Skip leading zeros */
|
||||
if ((val != 0) || (*len != 0)) {
|
||||
*p = val;
|
||||
(*len)++;
|
||||
p++;
|
||||
}
|
||||
|
||||
remaining_bytes--;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to generate the X509 certificate data for a private key
|
||||
*
|
||||
* Writes certificate in certCtx
|
||||
*
|
||||
* Based on programs/x509/cert_write.c
|
||||
*/
|
||||
|
||||
static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom, std::string validityTo) {
|
||||
int funcRes = 0;
|
||||
int stepRes = 0;
|
||||
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
mbedtls_pk_context key;
|
||||
mbedtls_x509write_cert crt;
|
||||
unsigned char * primary_buffer;
|
||||
unsigned char *certOffset;
|
||||
unsigned char * output_buffer;
|
||||
size_t certLength;
|
||||
const char *serial = "peer";
|
||||
size_t serial_len;
|
||||
|
||||
// Make a C-friendly version of the distinguished name
|
||||
char dn_cstr[dn.length()+1];
|
||||
strcpy(dn_cstr, dn.c_str());
|
||||
|
||||
std::string cn = get_cn(dn);
|
||||
if (cn == "") {
|
||||
return HTTPS_SERVER_ERROR_CERTGEN_CN;
|
||||
}
|
||||
|
||||
// Initialize the entropy source
|
||||
mbedtls_entropy_init( &entropy );
|
||||
|
||||
// Initialize the RNG
|
||||
mbedtls_ctr_drbg_init( &ctr_drbg );
|
||||
stepRes = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0 );
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_RNG;
|
||||
goto error_after_entropy;
|
||||
}
|
||||
|
||||
mbedtls_pk_init( &key );
|
||||
|
||||
stepRes = mbedtls_pk_parse_key( &key, (const unsigned char *)certCtx.getKeyPEM().c_str(), certCtx.getPKLength() + 1, NULL, 0, mbedtls_ctr_drbg_random, &ctr_drbg);
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_READKEY;
|
||||
goto error_after_key;
|
||||
}
|
||||
|
||||
// Start configuring the certificate
|
||||
mbedtls_x509write_crt_init( &crt );
|
||||
// Set version and hash algorithm
|
||||
mbedtls_x509write_crt_set_version( &crt, MBEDTLS_X509_CRT_VERSION_3 );
|
||||
mbedtls_x509write_crt_set_md_alg( &crt, MBEDTLS_MD_SHA256 );
|
||||
|
||||
// Set the keys (same key as we self-sign)
|
||||
mbedtls_x509write_crt_set_subject_key( &crt, &key );
|
||||
mbedtls_x509write_crt_set_issuer_key( &crt, &key );
|
||||
|
||||
// Set issuer and subject (same, as we self-sign)
|
||||
stepRes = mbedtls_x509write_crt_set_subject_name( &crt, dn_cstr );
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME;
|
||||
goto error_after_cert;
|
||||
}
|
||||
stepRes = mbedtls_x509write_crt_set_issuer_name( &crt, dn_cstr );
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME;
|
||||
goto error_after_cert;
|
||||
}
|
||||
|
||||
// Set the validity of the certificate. At the moment, it's fixed from 2019 to end of 2029.
|
||||
stepRes = mbedtls_x509write_crt_set_validity( &crt, validityFrom.c_str(), validityTo.c_str());
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_VALIDITY;
|
||||
goto error_after_cert;
|
||||
}
|
||||
|
||||
// Make this a CA certificate
|
||||
stepRes = mbedtls_x509write_crt_set_basic_constraints( &crt, 1, 0 );
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_VALIDITY;
|
||||
goto error_after_cert;
|
||||
}
|
||||
|
||||
stepRes = add_subject_alt_name( &crt, cn );
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME;
|
||||
goto error_after_cert;
|
||||
}
|
||||
|
||||
// Initialize the serial number
|
||||
stepRes = mbedtls_x509write_crt_set_serial_raw( &crt, (unsigned char *)serial, strlen(serial) );
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_SERIAL;
|
||||
goto error_after_cert_serial;
|
||||
}
|
||||
|
||||
// Create buffer to write the certificate
|
||||
primary_buffer = new unsigned char[4096];
|
||||
if (primary_buffer == NULL) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_OUT_OF_MEM;
|
||||
goto error_after_cert_serial;
|
||||
}
|
||||
|
||||
// Write the actual certificate
|
||||
stepRes = mbedtls_x509write_crt_pem(&crt, primary_buffer, 4096, mbedtls_ctr_drbg_random, &ctr_drbg );
|
||||
if (stepRes < 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_WRITE;
|
||||
goto error_after_primary_buffer;
|
||||
}
|
||||
|
||||
// Configure the cert in the context
|
||||
certCtx.setCert((char*)primary_buffer);
|
||||
|
||||
// Run through the cleanup process
|
||||
error_after_primary_buffer:
|
||||
delete[] primary_buffer;
|
||||
|
||||
error_after_cert_serial:
|
||||
|
||||
error_after_cert:
|
||||
mbedtls_x509write_crt_free( &crt );
|
||||
|
||||
error_after_key:
|
||||
mbedtls_pk_free(&key);
|
||||
|
||||
error_after_entropy:
|
||||
mbedtls_ctr_drbg_free( &ctr_drbg );
|
||||
mbedtls_entropy_free( &entropy );
|
||||
return funcRes;
|
||||
}
|
||||
|
||||
int createSelfSignedCert(SSLCert &certCtx, SSLKeySize keySize, std::string dn, std::string validFrom, std::string validUntil) {
|
||||
|
||||
// Add the private key
|
||||
int keyRes = gen_key(certCtx, keySize);
|
||||
if (keyRes != 0) {
|
||||
// Key-generation failed, return the failure code
|
||||
return keyRes;
|
||||
}
|
||||
|
||||
// Add the self-signed certificate
|
||||
int certRes = cert_write(certCtx, dn, validFrom, validUntil);
|
||||
if (certRes != 0) {
|
||||
// Cert writing failed, reset the pk and return failure code
|
||||
certCtx.setPK("");
|
||||
return certRes;
|
||||
}
|
||||
|
||||
// If all went well, return 0
|
||||
return 0;
|
||||
}
|
||||
89
src/util/SSLCert.hpp
Normal file
89
src/util/SSLCert.hpp
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Frank Hessel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef SRC_SSLCERT_HPP_
|
||||
#define SRC_SSLCERT_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <string>
|
||||
#include <mbedtls/rsa.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/pk.h>
|
||||
#include <mbedtls/x509.h>
|
||||
#include <mbedtls/x509_crt.h>
|
||||
#include <mbedtls/x509_csr.h>
|
||||
#include <mbedtls/asn1write.h>
|
||||
#include <mbedtls/oid.h>
|
||||
|
||||
#define HTTPS_SERVER_ERROR_KEYGEN 0x0F
|
||||
#define HTTPS_SERVER_ERROR_KEYGEN_RNG 0x02
|
||||
#define HTTPS_SERVER_ERROR_KEYGEN_SETUP_PK 0x03
|
||||
#define HTTPS_SERVER_ERROR_KEYGEN_GEN_PK 0x04
|
||||
#define HTTPS_SERVER_ERROR_KEY_WRITE_PK 0x05
|
||||
#define HTTPS_SERVER_ERROR_KEY_OUT_OF_MEM 0x06
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN 0x1F
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_RNG 0x12
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_READKEY 0x13
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_WRITE 0x15
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_OUT_OF_MEM 0x16
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_NAME 0x17
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_SERIAL 0x18
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_VALIDITY 0x19
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_CN 0x1a
|
||||
|
||||
class SSLCert {
|
||||
public:
|
||||
SSLCert(
|
||||
uint16_t certLength = 0,
|
||||
uint16_t pkLength = 0,
|
||||
String keyPEM = "",
|
||||
String certPEM = ""
|
||||
);
|
||||
virtual ~SSLCert();
|
||||
uint16_t getCertLength();
|
||||
uint16_t getPKLength();
|
||||
String getCertPEM();
|
||||
String getKeyPEM();
|
||||
void setPK(String _keyPEM);
|
||||
void setCert(String _certPEM);
|
||||
void clear();
|
||||
|
||||
private:
|
||||
uint16_t _certLength;
|
||||
uint16_t _pkLength;
|
||||
String _keyPEM;
|
||||
String _certPEM;
|
||||
};
|
||||
|
||||
enum SSLKeySize {
|
||||
KEYSIZE_1024 = 1024,
|
||||
KEYSIZE_2048 = 2048,
|
||||
KEYSIZE_4096 = 4096
|
||||
};
|
||||
|
||||
int createSelfSignedCert(SSLCert &certCtx, SSLKeySize keySize, std::string dn, std::string validFrom = "20190101000000", std::string validUntil = "20300101000000");
|
||||
|
||||
#endif /* SRC_SSLCERT_HPP_ */
|
||||
@@ -1 +1 @@
|
||||
# Espressif ESP32 Partition Table
|
||||
# Espressif ESP32 Partition Table
|
||||
|
@@ -8,6 +8,8 @@ def get_board_name(env):
|
||||
board = env.get('BOARD_MCU')
|
||||
if env.get('BOARD') == 'nuki-esp32solo1':
|
||||
board = 'esp32solo1'
|
||||
elif env.get('BOARD') == 'nuki-esp32gls10':
|
||||
board = 'esp32gls10'
|
||||
elif env.get('BOARD') == 'nuki-esp32-s3-oct':
|
||||
board = 'esp32s3oct'
|
||||
return board
|
||||
|
||||
@@ -13,7 +13,7 @@ default_envs = updater_esp32
|
||||
boards_dir = ../boards
|
||||
|
||||
[env]
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.13/platform-espressif32.zip
|
||||
platform_packages =
|
||||
framework = arduino, espidf
|
||||
board_build.embed_txtfiles =
|
||||
@@ -67,6 +67,13 @@ extra_scripts =
|
||||
board_build.cmake_extra_args =
|
||||
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.esp32"
|
||||
|
||||
[env:updater_esp32-gl-s10]
|
||||
extends = env:updater_esp32
|
||||
board = nuki-esp32gls10
|
||||
board_build.cmake_extra_args =
|
||||
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.esp32;sdkconfig.gls10.defaults"
|
||||
-DNUKI_TARGET_GL_S10=y
|
||||
|
||||
[env:updater_esp32-c3]
|
||||
extends = env:updater_esp32
|
||||
board = esp32-c3-devkitc-02
|
||||
|
||||
1
updater/sdkconfig.gls10.defaults
Normal file
1
updater/sdkconfig.gls10.defaults
Normal file
@@ -0,0 +1 @@
|
||||
CONFIG_D0WD_PSRAM_CLK_IO=6
|
||||
Reference in New Issue
Block a user