diff options
Diffstat (limited to 'source/storage/Session.mc')
| -rw-r--r-- | source/storage/Session.mc | 89 |
1 files changed, 89 insertions, 0 deletions
diff --git a/source/storage/Session.mc b/source/storage/Session.mc new file mode 100644 index 0000000..891b713 --- /dev/null +++ b/source/storage/Session.mc @@ -0,0 +1,89 @@ +// 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; + } +} |
