Skip to content

Commit da2fc0f

Browse files
committed
v2.4.1: Hidden Space integrity, Duress PIN dependency, RTC/display fixes
See CHANGELOG.md [2.4.1] for the full list of Added/Changed/Fixed/Security items across this release.
1 parent 097b2fc commit da2fc0f

41 files changed

Lines changed: 3561 additions & 1768 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,80 @@
11
# Changelog
22

3-
## [2.4.0] — May 2026
3+
## [2.4.1] — August 2026
4+
### Added
5+
- **Send Login Before Password** — new per-password option in Password Manager; when enabled, device types the login username/email first, then presses Tab or Enter (configurable), waits a specified delay (default 300 ms), and finally types the password. Useful for automated login to forms requiring both username and password fields. Configurable per entry in web cabinet: login field, navigation mode (Tab/Enter), and delay before password. LOGIN ⏎ or LOGIN ⇥ badge shown on device display and in web cabinet depending on navigation mode.
6+
- **Wildcard Passwords** — randomly generated passwords (3-50 chars, default 16) that never leave the device. Generated on-demand per boot session with cached reuse; ideal for accounts where only HID typing is needed, not clipboard access. Singleton constraint enforced (max 1 wildcard entry). Wildcard entries cannot be copied to clipboard or exported (UI guards in web cabinet and offline decrypt tool). Session cache zeroed using `String::setCharAt()` at shutdown, mode transitions, and post-transmission. RND badge (magenta) shown on device display.
7+
- **Secure Import/Export Mode** — TOTP keys and passwords are now exported/imported exclusively through an isolated, closed-AP session (reboot → PIN unlock → freshly generated, one-time WPA2 access point), replacing the old always-on 5-minute API-enable toggle. Deliberately outside the ordinary 8-layer web cabinet stack — smaller attack surface for a sensitive bulk-data operation, bounded by a 10-minute rolling timeout and a 30-minute hard cap. Import path validates the decrypted file's schema (TOTP keys vs. passwords) before writing, rejecting a mismatched file type with an explicit error instead of silently corrupting the store. Mirrors whichever Hidden Space is active — tagged to the requesting space so the mode only triggers for the space that requested it.
8+
9+
### Changed
10+
- **BREAKING:** Removed the legacy 5-minute "enable import/export" toggle and all six of its endpoints (`/api/enable_import_export`, `/api/import_export_status`, `/api/export`, `/api/import`, `/api/passwords/export`, `/api/passwords/import`) along with their tunnel/obfuscated variants. There is no replacement at these paths — all import/export now goes exclusively through Secure Import/Export Mode (see `### Added` above and `docs/development/ENDPOINTS.md`).
11+
- **Password flags migration** — `auto_send` flag migrated from standalone `bool` to bitmask (`uint16_t flags` in `PasswordEntry` struct). Existing entries are automatically migrated on first boot. No user action required.
12+
- **Heap pressure reduction** — `KeyManager::getAllKeys()` and `PasswordManager::getAllPasswords()` now return `const std::vector<T>&` instead of by-value copies, eliminating repeated full-vector allocations (previously up to ~8-20 KB per call, called as often as every 250ms from the main loop TOTP display). Sorting by `order` moved from the getters into `loadKeys()`/`loadPasswords()`, `reorderKeys()`/`reorderPasswords()`, and `replaceAllKeys()`/`replaceAllPasswords()` (import), since the getters no longer mutate state.
13+
- **Encrypted transport encoding** — the secure request/response envelope (`data`/`iv`/`tag` fields) now uses base64 instead of hex encoding, reducing payload size by ~30%. This is a protocol-breaking change: server and client must be updated together; existing sessions are invalidated on upgrade.
14+
- **Removed legacy `firmware/` directory** — firmware binaries have been served from `website/public/firmware/` (published via GitHub Pages) since the previous release; the old root-level `firmware/` copies were stale leftovers with no remaining references in the build workflow, web flasher, or documentation.
15+
16+
### Fixed
17+
- **TOTP/password reorder consistency** — fixed a latent bug where `reorderKeys()`/`reorderPasswords()` and key/password import did not re-sort the in-memory list after changing entry order, relying on the (now removed) sort-on-every-read behavior of the getters. Reordering and import now sort explicitly, so the new order is reflected immediately without requiring a reboot.
18+
- **Password export buffer overflow** — increased `decryptWithPassword()` JSON buffer from 2KB to 16KB to support exporting 50+ password entries. Previously, exports exceeding ~15-20 passwords would fail silently, returning `{"ciphertext": null}` or empty responses due to insufficient buffer space for base64-encoded ciphertext. The new limit supports up to ~100 password entries safely.
19+
- **Hidden Space files orphaned on removal** — `wipeHiddenSpace()` derived the HMAC paths of Space B's private files (`keys`, `passwords`, `wifi`, `session`, etc.) using whichever device key happened to be loaded, which was always Space A's key at every real call site. Since those paths are only correct when derived from Space B's own key, the files were never actually found or deleted — despite logs and the web response claiming a full wipe. Replaced with `removeHiddenSpaceWithPin(spaceBPin)`, which uses the existing non-destructive `tryDecryptSlotDry()` to obtain Space B's key into a local buffer without disturbing the caller's active Space A session, then addresses and deletes the files correctly. The sentinel file (intentionally addressed via Space A's key by design) is unaffected by this fix.
20+
- **Password screen not redrawn after QR code dismissal** — displaying a TOTP key's QR code (`/api/show_qr`) while the device was in Password mode left the screen blank after the 30-second auto-hide timer expired, or after manual dismissal via button press. TOTP mode already had an internal redraw flag consumed on its next code-refresh cycle; Password mode had no equivalent, so its own change-detection condition (index/revision unchanged) never fired. Added a mirrored, self-clearing redraw flag consumed on the next Password-mode loop iteration.
21+
- **False RTC "drifted" status on timezone mismatch** — `GET /api/rtc` serialized the RTC's UTC epoch through `localtime_r()` before sending it, embedding the device's configured timezone offset into a timestamp string with no UTC indicator. The browser then parsed that string in its own local timezone, so the client-side drift calculation silently combined two independent timezone offsets whenever the device timezone was unset or didn't match the browser's. Switched to `gmtime_r()` with a trailing `Z` suffix across all three response paths (standard, tunneled, obfuscated), so the client always receives and parses a true UTC timestamp.
22+
- **Duress PIN left as an orphan after disabling startup PIN** — the Duress PIN hash file and its enabled flag were untouched by `disablePinEncryption()`, so a configured Duress PIN remained on disk and "active" even though startup PIN's lock screen — its only possible entry point — no longer existed. Disabling the startup PIN now also removes `/duress_pin.hash`.
23+
24+
### Security Fixes
25+
- **PIN length disclosure mitigation** — PIN entry mask now displays fixed-width
26+
(10 slots) regardless of configured PIN length, preventing visual shoulder-surfing
27+
attacks. Previously, the number of dots/stars on screen revealed the exact PIN
28+
length. Auto-submit timing may still reveal approximate length to a live observer
29+
(documented known limitation, accepted UX trade-off for two-button interface).
30+
- **Startup PIN can no longer be disabled while Hidden Space exists** — disabling
31+
the startup PIN silently attempted to wipe Hidden Space using the wrong key (see
32+
Fixed above) and would have left an inconsistent, partially-decrypted device
33+
state. Disabling PIN is now hard-blocked, both server-side (`400 pin_required`-style
34+
early rejection before the on-device prompt) and on-device, while Hidden Space is
35+
provisioned; the web cabinet greys out and disables the toggle with an inline
36+
explanation. Removing Hidden Space is a separate, explicit action that now requires
37+
entering Space B's own PIN on-device before any deletion occurs.
38+
- **Hidden Space creation on an unencrypted device key** — `createHiddenSpace()`
39+
wrote its dual-slot structure at a fixed file offset without checking that the
40+
existing device key file was already in PIN-encrypted format. On a device with
41+
the startup PIN disabled (33-byte unencrypted key file), this silently corrupted
42+
the key file instead of failing. Added an `isDeviceKeyEncrypted()` guard at every
43+
entry point — all three web handlers, the on-device provisioning flow, and inside
44+
`createHiddenSpace()` itself as defense-in-depth — plus a greyed-out, disabled
45+
Hidden Space section in the web cabinet when the startup PIN is off.
46+
- **Duress PIN configurable without startup PIN enabled** — `POST /api/duress_pin_update`
47+
had no dependency on startup PIN state, allowing a Duress PIN to be set up even
48+
though it can only ever be triggered from the startup PIN lock screen. Added an
49+
`isDeviceKeyEncrypted()` guard to all three web handlers (`400` early rejection),
50+
plus a greyed-out, disabled Duress PIN section in the web cabinet, matching the
51+
Hidden Space guard pattern above.
52+
53+
### Documentation
54+
- Updated [API Endpoints](docs/development/ENDPOINTS.md) — added `send_login`, `login`, `nav_mode`, `login_delay_ms` parameters to `/api/passwords/*` endpoints; added `wildcard` and `wildcard_len` parameters with immutability notes for update operations
55+
- Updated [User Guide (EN)](docs/user/GUIDE.html) — added "Send Login Before Password" section with navigation mode explanations; Wildcard Passwords section already present
56+
- Updated [User Guide (RU)](docs/user/GUIDE.ru.html) — added "Отправка логина перед паролем" section; Wildcard-пароли section already present
57+
- Updated [Decrypt Export Guide](docs/user/decrypt-export-guide.md) — Wildcard passwords section with copy/export restrictions already present
58+
- Updated [Security Overview](docs/development/security/SECURITY_OVERVIEW.md) —
59+
added PIN length timing disclosure to Known Limitations section; added wildcard password RAM zeroing limitation (`setCharAt()` vs `secure_memzero()` distinction)
60+
- Updated [Security Model](docs/development/security/security_model.md) —
61+
added PIN length via timing to Threat Model (Out of Scope); added comprehensive Wildcard Passwords section covering singleton constraint, session-based generation, RAM zeroing points, and platform limitations
62+
- Updated [System Design](docs/development/system_design.md) — clarified
63+
`/.sys_ui_prefs` file description regarding fixed-width mask implementation; added ENT, L/T, L/E, and RND badges to Password Security Badges table; updated `secureShutdown()` section with wildcard session wipe details
64+
- Updated [Modes Guide](docs/user/MODES.md) — added note on wildcard password behavior consistency across network modes with export restriction clarification
65+
- Updated [README.md](README.md) — added Wildcard passwords to Password Manager feature list
66+
- Updated [ENDPOINTS.md](docs/development/ENDPOINTS.md) — replaced the legacy 6-endpoint import/export API reference with Secure Import/Export Mode's routes
67+
- Updated [SECURITY_OVERVIEW.md](docs/development/security/SECURITY_OVERVIEW.md) — added "Import/export security model" section explaining the closed-AP, no-Layer-4 trade-off
68+
- Updated [README.md](README.md) — clarified bulk import feature description to reference Secure Import/Export Mode
69+
- Updated [Security Overview](docs/development/security/SECURITY_OVERVIEW.md) — corrected Hidden Space section: PIN disable is now blocked (not auto-wiped) while Hidden Space is provisioned
70+
- Updated [Security Model](docs/development/security/security_model.md) — replaced references to `wipeHiddenSpace()` with `removeHiddenSpaceWithPin()`, corrected the calling-context description
71+
- Updated [API Endpoints](docs/development/ENDPOINTS.md) — corrected `/api/hidden_space` disable action: removed the inaccurate "must be called from Space B context" claim, documented the new `prompt_on_device` deferred-confirmation response
72+
- Updated [User Guide (EN)](docs/user/GUIDE.html) and [User Guide (RU)](docs/user/GUIDE.ru.html) — corrected Hidden Space section: PIN disable is blocked, not automatic, while Hidden Space is active
73+
- Updated [Modes Guide](docs/user/MODES.md) — corrected Hidden Space constraint description to match blocked (not automatic-wipe) behavior
74+
75+
---
76+
77+
## [2.4.0] — June 2026
478
### Added
579
- **Hidden Space** — second independent encrypted vault selectable at boot via
680
alternate PIN. Each space has isolated TOTP keys, passwords, web cabinet

‎README.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@
172172
- Compatible with Google Authenticator, Microsoft Authenticator, Authy, and all RFC 6238 / RFC 4226 services
173173
- SHA1 / SHA256 / SHA512, 6 and 8 digit codes, 30s and 60s periods
174174
- HOTP counter-based codes with automatic counter increment
175-
- Add keys via QR code scan (camera or file), manual entry, or bulk import
175+
- Add keys via QR code scan (camera or file), manual entry, or bulk import (via Secure Import/Export Mode)
176176
- Export any key as QR code — displayed on the device screen and in the web interface
177177
- Encrypted storage with unique per-device key
178178

@@ -181,6 +181,7 @@
181181
- **BLE HID keyboard** (ESP32 & S3): types passwords directly into any device, no clipboard
182182
- **USB HID keyboard** (S3 only): native USB connection, no pairing needed
183183
- PIN protection for BLE transmission
184+
- **Wildcard passwords**: randomly generated on-device passwords that never leave the device — ideal for accounts where you only need HID typing, not clipboard access
184185
- Encrypted export/import for backup and migration
185186

186187
### 🔒 Hidden Space
@@ -319,11 +320,11 @@ pio run -e lilygo-t-display-s3 -t upload
319320
- Display settings in web interface (brightness)
320321

321322
### Security Enhancements
322-
- **Export with physical presence confirmation** — export requires button press on
323-
device; ephemeral key derived on-device, never entered manually
324323
- Flash encryption and secure boot (optional hardening)
325324
- ATECC608 secure element support
326325
- SD Card Module Support for pin code + cryptokey unlock feature
326+
- ZW111 Fingerprint module support
327+
- Add support extra keyboard layouts for BLE send
327328

328329
### Cryptography
329330
- Migration ECDH P-256 → X25519 (~400ms → ~80ms key exchange)

0 commit comments

Comments
 (0)