mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-18 11:32:11 +00:00
Merge PR #37: lock screen PIN (3urobeat), adapted
The contributor's feature -- a PIN asked before the screen unlocks, on
boot, and when the magnet cover opens, stored as a salted SHA-256 -- on
top of this branch, with:
- ui-core/ScreenLock.h: set / check / isSet and the tries limit, shared by
L1 and L2. isSet looks at every byte (the PR tested only the first, so one
PIN in 256 silently disabled the lock). 5 misses pause entry for 30 s.
- NodePrefs: the fields at the tail after quiet hours, sentinel 0xC0DE0031,
sizeof 2880; an older file's bytes there are cleared. A sentinel bump no
longer resets display_brightness_pct from a file that already has it.
- DataStore: no AGPL header (the project is MIT), no whitespace churn.
- Keyboard: the number pad is a widget flag (pin_mode) with a prompt in the
preview ("New PIN", "Again", "PIN"), not keyboard_type = 2 written into the
prefs and restored after.
- L1 Settings: "Lock PIN" right after the schema's "Lock screen" row, typed
twice, at least 4 characters, saved at once (a power-off right after
setting it kept no PIN). Home is told about a boot-time lock once it
exists (also fixes booting with the cover closed).
- L2: the screen PIN moves from plain text in NVS to the same hash in
NodePrefs; the old one is migrated on first boot and removed. The pad
unlocks as soon as the hash matches from 4 digits on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
// The screen lock's PIN, for every frontend: stored in NodePrefs as a salted
|
||||
// SHA-256 (lock_screen_password / _salt), never as the PIN itself. It locks
|
||||
// the screen only -- the radio and the app link keep working.
|
||||
|
||||
#include <string.h>
|
||||
#include <Arduino.h>
|
||||
#include <Utils.h>
|
||||
#include "../NodePrefs.h"
|
||||
|
||||
namespace screenlock {
|
||||
|
||||
static const uint8_t TRIES = 5; // misses before a pause
|
||||
static const uint32_t PAUSE_MS = 30000;
|
||||
|
||||
// Any byte set: a digest can start with a zero byte.
|
||||
static bool isSet(const NodePrefs& p) {
|
||||
for (uint8_t b : p.lock_screen_password) if (b) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void clear(NodePrefs& p) {
|
||||
memset(p.lock_screen_password, 0, sizeof(p.lock_screen_password));
|
||||
memset(p.lock_screen_password_salt, 0, sizeof(p.lock_screen_password_salt));
|
||||
}
|
||||
|
||||
// A new PIN under a fresh salt; "" clears it. The caller saves the prefs.
|
||||
static void set(NodePrefs& p, const char* pin, mesh::RNG* rng) {
|
||||
if (!pin || !pin[0]) { clear(p); return; }
|
||||
if (rng) {
|
||||
rng->random(p.lock_screen_password_salt, sizeof(p.lock_screen_password_salt));
|
||||
} else {
|
||||
uint32_t t = micros() ^ (millis() << 16);
|
||||
memcpy(p.lock_screen_password_salt, &t, sizeof(t));
|
||||
}
|
||||
mesh::Utils::sha256(p.lock_screen_password, sizeof(p.lock_screen_password),
|
||||
p.lock_screen_password_salt, sizeof(p.lock_screen_password_salt),
|
||||
(const uint8_t*)pin, (int)strlen(pin));
|
||||
}
|
||||
|
||||
static bool check(const NodePrefs& p, const char* pin) {
|
||||
if (!pin) return false;
|
||||
uint8_t digest[NodePrefs::LOCK_HASH_LEN];
|
||||
mesh::Utils::sha256(digest, sizeof(digest),
|
||||
p.lock_screen_password_salt, sizeof(p.lock_screen_password_salt),
|
||||
(const uint8_t*)pin, (int)strlen(pin));
|
||||
return memcmp(digest, p.lock_screen_password, sizeof(digest)) == 0;
|
||||
}
|
||||
|
||||
// Wrong tries: TRIES misses in a row, then PAUSE_MS before the next one.
|
||||
struct Attempts {
|
||||
uint8_t fails = 0;
|
||||
uint32_t block_until = 0; // millis(); 0 = not blocked
|
||||
|
||||
bool blocked() {
|
||||
if (block_until && (int32_t)(block_until - millis()) > 0) return true;
|
||||
block_until = 0;
|
||||
return false;
|
||||
}
|
||||
uint32_t secondsLeft() const {
|
||||
int32_t left = (int32_t)(block_until - millis());
|
||||
return block_until && left > 0 ? (left + 999) / 1000 : 0;
|
||||
}
|
||||
void ok() { fails = 0; block_until = 0; }
|
||||
// A miss; true when it starts the pause.
|
||||
bool miss() {
|
||||
if (++fails < TRIES) return false;
|
||||
fails = 0;
|
||||
block_until = (millis() + PAUSE_MS) | 1;
|
||||
return true;
|
||||
}
|
||||
uint8_t left() const { return TRIES - fails; }
|
||||
};
|
||||
|
||||
} // namespace screenlock
|
||||
Reference in New Issue
Block a user