// Author: cirakg & claude ai // Summary: The unlocked session: holds the master key in RAM and locks after inactivity or on request. // // README 7.5: unlocking puts the locked master key (and its derived keys) in // RAM; locking zeroes and drops them. The UI calls touch() on every user // action and checkExpiry() from its 1 Hz timer; the app calls lock() in // onStop. An auto-lock time of 0 means "Immediately": the UI locks as soon as // you leave a locked entry, and checkExpiry() locks on its next call. import Toybox.Lang; class Session { static const DEFAULT_AUTO_LOCK_MS = 60000; // Inactivity time before locking, in ms (0 = immediately). var autoLockMs as Number = DEFAULT_AUTO_LOCK_MS; private var _time as TimeSource; private var _masterKey as ByteArray? = null; private var _keys as SealedBox.Keys? = null; private var _lastActivity as Number = 0; function initialize(time as TimeSource) { _time = time; } // Take ownership of the master key (the caller must not wipe or keep it). function unlock(masterKey as ByteArray) as Void { lock(); _masterKey = masterKey; _keys = new SealedBox.Keys(masterKey); touch(); } function isUnlocked() as Boolean { return _keys != null; } // The master key's derived keys. Throws VaultError LOCKED when locked. function keys() as SealedBox.Keys { var keys = _keys; if (keys == null) { throw new VaultError(VaultError.LOCKED, "locked"); } return keys; } // The master key itself (only to re-wrap it under a new PIN). Throws // VaultError LOCKED when locked. function masterKey() as ByteArray { var key = _masterKey; if (key == null) { throw new VaultError(VaultError.LOCKED, "locked"); } return key; } // Note user activity; restarts the inactivity timeout. function touch() as Void { _lastActivity = _time.uptimeMs(); } // Lock if the inactivity timeout has passed. Returns true when this call // locked the session, so the UI can close open locked entries. function checkExpiry() as Boolean { if (!isUnlocked()) { return false; } var now = _time.uptimeMs(); // A timer that went backwards (watch restart, wrap) counts as expired. if (now < _lastActivity || now - _lastActivity >= autoLockMs) { lock(); return true; } return false; } // Zero and drop every key. Safe to call when already locked. function lock() as Void { var keys = _keys; if (keys != null) { keys.wipe(); } Bytes.wipe(_masterKey); _keys = null; _masterKey = null; } }