mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-09 11:16:39 +00:00
feat: Hash & salt lock screen password
This commit is contained in:
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* File: DataStore.cpp
|
||||
* Project: companion_radio
|
||||
* Created Date: 2026-09-26 12:03:18
|
||||
* Author: 3urobeat
|
||||
*
|
||||
* Last Modified: 2026-09-26 12:16:49
|
||||
* Modified By: 3urobeat
|
||||
*
|
||||
* Copyright (c) 2026 3urobeat <https://github.com/3urobeat>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "DataStore.h"
|
||||
#include "Features.h" // FEAT_JOYSTICK_ROTATION_SETTING (else `#if !FEAT_…` is always true)
|
||||
@@ -624,8 +641,10 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
|
||||
// append the lock-screen password. Should be empty by default
|
||||
// since struct was zero initialized in begin(), meaning password is disabled
|
||||
// → 0xC0DE002F: append the lock-screen password
|
||||
// → 0xC0DE0030: append the per-device password salt
|
||||
rd(_prefs.lock_screen_password, sizeof(_prefs.lock_screen_password));
|
||||
_prefs.lock_screen_password[sizeof(_prefs.lock_screen_password) - 1] = '\0';
|
||||
rd(_prefs.lock_screen_password_salt, sizeof(_prefs.lock_screen_password_salt));
|
||||
|
||||
// Schema sentinel: bumped on layout changes. Mismatch means an older file
|
||||
// (or a different schema); rd() and the clamps above already keep every
|
||||
@@ -825,6 +844,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
file.write((uint8_t *)&_prefs.loc_share_scope, sizeof(_prefs.loc_share_scope));
|
||||
file.write((uint8_t *)&_prefs.loc_share_duration_idx, sizeof(_prefs.loc_share_duration_idx));
|
||||
file.write((uint8_t *)_prefs.lock_screen_password, sizeof(_prefs.lock_screen_password));
|
||||
file.write((uint8_t *)_prefs.lock_screen_password_salt, sizeof(_prefs.lock_screen_password_salt));
|
||||
|
||||
// Tail sentinel — must be last. See NodePrefs::SCHEMA_SENTINEL. Its write is
|
||||
// the one we check: once the flash fills, writes return 0, so a good
|
||||
|
||||
@@ -539,7 +539,9 @@ struct NodePrefs { // persisted to file
|
||||
// Lock-screen password, empty by default. If set, a password is required to
|
||||
// unlock the lock screen.
|
||||
static const uint8_t LOCK_PASSWORD_MAX_LEN = 32;
|
||||
char lock_screen_password[LOCK_PASSWORD_MAX_LEN];
|
||||
static const uint8_t lock_screen_password_salt_LEN = 16;
|
||||
uint8_t lock_screen_password[LOCK_PASSWORD_MAX_LEN];
|
||||
uint8_t lock_screen_password_salt[lock_screen_password_salt_LEN];
|
||||
|
||||
// Single source of truth for the live-share option tables (shared by the Map
|
||||
// UI labels and the auto-send engine in UITask).
|
||||
@@ -628,7 +630,7 @@ struct NodePrefs { // persisted to file
|
||||
// repeat_* fields) instead of at the tail, which shifted every field after
|
||||
// them by 25 bytes when loading an older file. Never released, but a dev
|
||||
// build wrote it, so the number must not be reused for anything else.
|
||||
static const uint32_t SCHEMA_SENTINEL = 0xC0DE002F;
|
||||
static const uint32_t SCHEMA_SENTINEL = 0xC0DE0030;
|
||||
|
||||
// Bit-index for each home page. Used by page_order (entries store bit+1) and
|
||||
// by home_pages_mask. Single source of truth — both HomeScreen::pageBit/bitToPage
|
||||
@@ -784,7 +786,8 @@ struct NodePrefs { // persisted to file
|
||||
// (ESP32) builds, sizeof unchanged at 2824. loc_share_duration_idx (0xC0DE002E)
|
||||
// likewise (sim build; see the check below).
|
||||
// 0xC0DE002F 32 byte bump for lock_screen_password
|
||||
static_assert(sizeof(NodePrefs) == 2856,
|
||||
// 0xC0DE0030 16 byte bump for lock_screen_password_salt
|
||||
static_assert(sizeof(NodePrefs) == 2872,
|
||||
"NodePrefs layout changed — sync DataStore save/load + clamp, bump "
|
||||
"SCHEMA_SENTINEL, then update this size (see steps above).");
|
||||
|
||||
|
||||
@@ -873,8 +873,7 @@ public:
|
||||
auto res = _kb->handleInput(c);
|
||||
if (res == KeyboardWidget::DONE) {
|
||||
if (p) {
|
||||
strncpy(p->lock_screen_password, _kb->buf, sizeof(p->lock_screen_password) - 1);
|
||||
p->lock_screen_password[sizeof(p->lock_screen_password) - 1] = '\0';
|
||||
_task->setNodeLockPassword(_kb->buf);
|
||||
_dirty = true; // savePrefsIfDirty persists new password
|
||||
}
|
||||
_edit_lock_pass = false;
|
||||
@@ -1137,7 +1136,7 @@ public:
|
||||
// LockPass: Clear password if defined or get input from keyboard
|
||||
if (_selected == LOCK_PASSWORD && p && enter) {
|
||||
if (p->lock_screen_password[0]) {
|
||||
p->lock_screen_password[0] = '\0';
|
||||
_task->setNodeLockPassword("");
|
||||
_dirty = true;
|
||||
} else {
|
||||
_edit_lock_pass = true;
|
||||
|
||||
@@ -2224,6 +2224,39 @@ bool UITask::passwordLockEnabled() const {
|
||||
return _node_prefs && _node_prefs->lock_screen_password[0] != '\0';
|
||||
}
|
||||
|
||||
void UITask::setNodeLockPassword(const char* plain) {
|
||||
if (!_node_prefs || !plain) return;
|
||||
if (plain[0] == '\0') { // Clear the password
|
||||
memset(_node_prefs->lock_screen_password, 0, sizeof(_node_prefs->lock_screen_password));
|
||||
memset(_node_prefs->lock_screen_password_salt, 0, sizeof(_node_prefs->lock_screen_password_salt));
|
||||
return;
|
||||
}
|
||||
// Generate a fresh random salt and store salted SHA-256 digest
|
||||
mesh::RNG* rng = the_mesh.getRNG();
|
||||
if (rng) {
|
||||
rng->random(_node_prefs->lock_screen_password_salt, sizeof(_node_prefs->lock_screen_password_salt));
|
||||
} else {
|
||||
// Time as fallback entropy source
|
||||
uint32_t t = (uint32_t)millis() ^ (uint32_t)rtc_clock.getCurrentTime();
|
||||
memcpy(_node_prefs->lock_screen_password_salt, &t, sizeof(t));
|
||||
}
|
||||
mesh::Utils::sha256((uint8_t*)_node_prefs->lock_screen_password,
|
||||
sizeof(_node_prefs->lock_screen_password),
|
||||
_node_prefs->lock_screen_password_salt,
|
||||
sizeof(_node_prefs->lock_screen_password_salt),
|
||||
(const uint8_t*)plain, (int)strlen(plain));
|
||||
}
|
||||
|
||||
bool UITask::checkNodeLockPassword(const char* entered) const {
|
||||
if (!_node_prefs || !entered) return false;
|
||||
uint8_t digest[NodePrefs::LOCK_PASSWORD_MAX_LEN];
|
||||
mesh::Utils::sha256(digest, sizeof(digest),
|
||||
_node_prefs->lock_screen_password_salt,
|
||||
sizeof(_node_prefs->lock_screen_password_salt),
|
||||
(const uint8_t*)entered, (int)strlen(entered));
|
||||
return memcmp(digest, _node_prefs->lock_screen_password, sizeof(digest)) == 0;
|
||||
}
|
||||
|
||||
void UITask::beginUnlockPrompt() {
|
||||
_unlock_kb = true; // Track that keyboard is visible and is waiting for input
|
||||
int max_len = _node_prefs ? (int)sizeof(_node_prefs->lock_screen_password) - 1 : 32;
|
||||
@@ -2241,7 +2274,7 @@ void UITask::cancelUnlockPrompt() {
|
||||
void UITask::handleUnlockKey(char c) {
|
||||
auto res = _kb.handleInput(c);
|
||||
if (res == KeyboardWidget::DONE) { // Process input on submit
|
||||
if (_node_prefs && strcmp(_kb.buf, _node_prefs->lock_screen_password) == 0) {
|
||||
if (_node_prefs && checkNodeLockPassword(_kb.buf)) {
|
||||
// Match: Unlock
|
||||
_unlock_kb = false;
|
||||
_locked = false;
|
||||
|
||||
@@ -306,6 +306,11 @@ private:
|
||||
void renderAlertOverlay();
|
||||
|
||||
public:
|
||||
// Stores new lock screen salted SHA-256 password
|
||||
// `plain` is the raw user input, passing "" clears currently set password
|
||||
void setNodeLockPassword(const char* plain);
|
||||
// Verifies entered against the stored password hash. Returns match as bool
|
||||
bool checkNodeLockPassword(const char* entered) const;
|
||||
|
||||
UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL), _node_prefs(NULL) {
|
||||
next_batt_chck = _next_refresh = 0;
|
||||
|
||||
Reference in New Issue
Block a user