summaryrefslogtreecommitdiff
path: root/source/settings/ChangeQueue.mc
diff options
context:
space:
mode:
Diffstat (limited to 'source/settings/ChangeQueue.mc')
-rw-r--r--source/settings/ChangeQueue.mc76
1 files changed, 76 insertions, 0 deletions
diff --git a/source/settings/ChangeQueue.mc b/source/settings/ChangeQueue.mc
index 12f88f7..c5919f6 100644
--- a/source/settings/ChangeQueue.mc
+++ b/source/settings/ChangeQueue.mc
@@ -8,6 +8,11 @@
// own with the device key (aad "OTPW-Q-1"), so a damaged item costs only
// itself. A new secret in an item is no more exposed than it was in the
// settings it came from, as with pending entries (README 7.7).
+//
+// "Require PIN" waits here only while the watch is locked (it needs the
+// master key). It only adds protection, so applyLocks() applies it at the
+// next correct (or newly set) PIN, without "Confirm all", the way pending
+// entries are promoted.
import Toybox.Application;
import Toybox.Lang;
@@ -18,6 +23,10 @@ module ChangeQueue {
const MAX_ITEMS = 16;
const AAD = "OTPW-Q-1";
+ // What applyLocks() did at the last PIN, for the results of a "Confirm
+ // all" that follows it (ChangeQueueJob). Replaced at every PIN.
+ var _lockLines as Array<String> = [] as Array<String>;
+
function count(vault as Vault) as Number {
var stored = vault.storage().getValue(KEY);
return stored instanceof Array ? stored.size() : 0;
@@ -75,6 +84,71 @@ module ChangeQueue {
return out;
}
+ // The number of waiting "Require PIN" actions (applied at the next PIN).
+ function countLocks(vault as Vault) as Number {
+ var actions = load(vault);
+ var n = 0;
+ for (var i = 0; i < actions.size(); i++) {
+ if (actions[i].action == ManageAction.LOCK) {
+ n++;
+ }
+ actions[i].wipe();
+ }
+ return n;
+ }
+
+ // After a correct or new PIN (session unlocked): apply the waiting
+ // "Require PIN" actions and remove them from the queue; the other items
+ // keep waiting, in order. Each is one re-seal (ManageRun, no code
+ // check), like promotePending. One that fails (the entry is gone or
+ // already locked) is dropped too.
+ function applyLocks(vault as Vault) as Void {
+ _lockLines = [] as Array<String>;
+ var stored = vault.storage().getValue(KEY);
+ if (!(stored instanceof Array) || !vault.session.isUnlocked()) {
+ return;
+ }
+ var keys = vault.keyManager.deviceKeys();
+ var aad = Bytes.fromUtf8(AAD);
+ var kept = [] as Array<Application.Storage.ValueType>;
+ for (var i = 0; i < stored.size(); i++) {
+ var box = stored[i];
+ var plain = box instanceof ByteArray ? SealedBox.open(keys, aad, box) : null;
+ var action = plain != null ? ManageAction.fromBytes(plain) : null;
+ Bytes.wipe(plain);
+ if (action != null && action.action == ManageAction.LOCK) {
+ var run = new ManageRun(vault, action, true);
+ Jobs.runToCompletion(run, 1);
+ _lockLines.add(run.failed ? "Not applied: " + action.describe() + ". " + run.message : run.message);
+ } else {
+ if (action != null) {
+ action.wipe();
+ }
+ kept.add(box as Application.Storage.ValueType); // damaged items are dropped by the next clear()
+ }
+ }
+ if (kept.size() == stored.size()) {
+ return;
+ }
+ if (kept.size() == 0) {
+ clear(vault);
+ return;
+ }
+ try {
+ vault.storage().setValue(KEY, kept);
+ } catch (e instanceof Lang.Exception) {
+ // Can't happen when shrinking; if it did, the locks just apply
+ // again at the next PIN ("already requires the PIN").
+ }
+ }
+
+ // The lines of the last applyLocks(), once (then forgotten).
+ function takeLockLines() as Array<String> {
+ var lines = _lockLines;
+ _lockLines = [] as Array<String>;
+ return lines;
+ }
+
function clear(vault as Vault) as Void {
vault.storage().deleteValue(KEY);
}
@@ -115,6 +189,8 @@ class ChangeQueueJob extends Jobs.Job {
var stored = vault.storage().getValue(ChangeQueue.KEY);
_loaded = stored instanceof Array ? stored.size() : 0;
_actions = ChangeQueue.load(vault);
+ // "Require PIN" changes the PIN just applied (ChangeQueue.applyLocks).
+ lines.addAll(ChangeQueue.takeLockLines());
}
function step(budget as Number) as Boolean {