aboutsummaryrefslogtreecommitdiff
path: root/source/storage/Session.mc
blob: 891b71395a8313164e4b0c37d4b0bc63c426bbd7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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;
    }
}