summaryrefslogtreecommitdiff
path: root/source/storage/Session.mc
diff options
context:
space:
mode:
Diffstat (limited to 'source/storage/Session.mc')
-rw-r--r--source/storage/Session.mc89
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;
+ }
+}