aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/crypto/KdfAesTest.mc2
-rw-r--r--test/ui/GlanceCodeTest.mc151
2 files changed, 152 insertions, 1 deletions
diff --git a/test/crypto/KdfAesTest.mc b/test/crypto/KdfAesTest.mc
index 6587bc9..176158d 100644
--- a/test/crypto/KdfAesTest.mc
+++ b/test/crypto/KdfAesTest.mc
@@ -56,7 +56,7 @@ function hkdfMatchesRfcVectors(logger as Test.Logger) as Boolean {
var ok = true;
for (var i = 0; i < TestVectors.HKDF.size(); i++) {
var r = TestSupport.row(TestVectors.HKDF, i);
- var okm = Kdf.hkdf(TestSupport.bytes(r, 0), TestSupport.bytes(r, 1), TestSupport.bytes(r, 2), TestSupport.num(r, 3));
+ var okm = Hkdf.derive(TestSupport.bytes(r, 0), TestSupport.bytes(r, 1), TestSupport.bytes(r, 2), TestSupport.num(r, 3));
ok = TestSupport.sameBytes(logger, "HKDF vector " + i, okm, TestSupport.bytes(r, 4)) && ok;
}
return ok;
diff --git a/test/ui/GlanceCodeTest.mc b/test/ui/GlanceCodeTest.mc
new file mode 100644
index 0000000..25c7dd7
--- /dev/null
+++ b/test/ui/GlanceCodeTest.mc
@@ -0,0 +1,151 @@
+// Author: cirakg & claude ai
+// Summary: Tests for the glance code: the sealed item round trip and its codes, tampering, the scan's choice of entry, the option, and the Manage favourite actions.
+
+import Toybox.Lang;
+import Toybox.Test;
+
+(:test)
+module GlanceFixtures {
+
+ const NOW = 1800000026l;
+
+ // The item the glance would read from the store, or null.
+ function item(v as Vault) as GlanceCode.Item? {
+ var store = v.storage();
+ var dk = store.getValue(GlanceCode.DEVICE_KEY);
+ var sealed = store.getValue(GlanceCode.KEY);
+ return GlanceCode.open(dk instanceof ByteArray ? dk : null, sealed instanceof ByteArray ? sealed : null);
+ }
+
+ function favorite(e as Entry) as Entry {
+ e.favorite = true;
+ return e;
+ }
+}
+
+// The glance item opens to the entry's label, and its codes are the
+// entry's codes (TOTP with SHA-256, 8 digits, 60 s; Steam).
+(:test)
+function glanceItemRoundTripsWithTheSameCodes(logger as Test.Logger) as Boolean {
+ var vault = SettingsFixtures.vault();
+ vault.glanceCode = true;
+ var github = vault.add(StorageFixtures.totp("GitHub"));
+ var steam = vault.add(StorageFixtures.steam("Steam"));
+ GlanceStore.update(vault, github);
+ var item = GlanceFixtures.item(vault);
+ var secret = Bytes.fromUtf8("12345678901234567890");
+ var now = GlanceFixtures.NOW;
+ if (item == null) {
+ logger.error("no item");
+ return false;
+ }
+ var totpOk = item.label.equals("GitHub") && item.code(now).equals(Otp.totp(Hashes.SHA256, secret, now, 60, 8))
+ && item.code(now).length() == 8 && item.counter(now) == now / 60 && item.secondsLeft(now) == 60 - (now % 60).toNumber();
+ GlanceStore.update(vault, steam);
+ var s = GlanceFixtures.item(vault);
+ var steamOk = s != null && s.code(now).equals(Otp.steam(secret, now)) && s.periodSeconds() == 30;
+ return totpOk && steamOk && GlanceCode.TYPE_STEAM == Entry.OTP_STEAM && GlanceCode.DEVICE_KEY.equals(KeyManager.DEVICE_KEY);
+}
+
+// A changed byte, another device key or another format gives nothing.
+(:test)
+function glanceItemRefusesTamperingAndOtherKeys(logger as Test.Logger) as Boolean {
+ var vault = SettingsFixtures.vault();
+ vault.glanceCode = true;
+ GlanceStore.update(vault, vault.add(StorageFixtures.totp("GitHub")));
+ var store = vault.storage();
+ var sealed = store.getValue(GlanceCode.KEY) as ByteArray;
+ var dk = store.getValue(GlanceCode.DEVICE_KEY) as ByteArray;
+ var tampered = Bytes.copy(sealed);
+ tampered[20] = tampered[20] ^ 1;
+ var otherKey = Bytes.copy(dk);
+ otherKey[0] = otherKey[0] ^ 1;
+ var bad = GlanceCode.encode("x", 0, 9, 6, 30, [1]b); // algorithm 9 doesn't exist
+ return GlanceCode.open(dk, sealed) != null && GlanceCode.open(dk, tampered) == null
+ && GlanceCode.open(otherKey, sealed) == null && GlanceCode.open(null, sealed) == null
+ && GlanceCode.open(dk, null) == null && GlanceCode.decode(bad) == null
+ && GlanceCode.decode(GlanceCode.encode("x", 0, 0, 6, 0, [1]b)) == null;
+}
+
+// The About scan picks the first favourite TOTP or Steam entry without the
+// PIN (not a locked, HOTP or barcode favourite), and removes the item when
+// the option goes off or the entry is no longer a favourite.
+(:test)
+function glanceScanChoosesTheFirstQualifyingFavourite(logger as Test.Logger) as Boolean {
+ var vault = SettingsFixtures.vault();
+ StorageFixtures.setPin(vault, StorageFixtures.PIN);
+ vault.glanceCode = true;
+ var locked = GlanceFixtures.favorite(StorageFixtures.totp("Bank"));
+ locked.locked = true;
+ vault.add(locked);
+ vault.add(GlanceFixtures.favorite(StorageFixtures.hotp("VPN", 3l)));
+ vault.add(StorageFixtures.code("Gym")); // a favourite barcode
+ vault.add(StorageFixtures.totp("Plain"));
+ var mail = vault.add(GlanceFixtures.favorite(StorageFixtures.totp("Mail")));
+ var s = SettingsFixtures.store();
+ SettingsFixtures.run(vault, s);
+ var chosen = GlanceFixtures.item(vault);
+ var first = chosen != null && chosen.label.equals("Mail");
+ // "Remove favourite" from the phone: the item goes.
+ SettingsFixtures.manage(s, mail, ManageAction.UNFAVORITE);
+ var job = SettingsFixtures.run(vault, s);
+ var gone = vault.storage().getValue(GlanceCode.KEY) == null && SettingsFixtures.text(s, ManageForm.STATUS).find("no longer a favourite") != null;
+ // Marked again, then the option off: the item goes too.
+ SettingsFixtures.manage(s, mail.toLower(), ManageAction.FAVORITE);
+ SettingsFixtures.run(vault, s);
+ var back = GlanceFixtures.item(vault) != null;
+ vault.glanceCode = false;
+ var needed = AboutScan.needed(vault);
+ SettingsFixtures.run(vault, s);
+ var off = vault.storage().getValue(GlanceCode.KEY) == null;
+ if (!(first && gone && back && needed && off)) {
+ logger.error("first " + first + " gone " + gone + " (" + job.lines + ") back " + back + " needed " + needed + " off " + off);
+ return false;
+ }
+ return true;
+}
+
+// Favourite actions check the entry's current mark, and wait for the PIN
+// on a locked entry like the other changes.
+(:test)
+function manageFavouriteActions(logger as Test.Logger) as Boolean {
+ var vault = ManageFixtures.vault();
+ var github = vault.ids()[0];
+ var s = ManageFixtures.send(vault, github, ManageAction.UNFAVORITE);
+ SettingsFixtures.run(vault, s);
+ var notYet = SettingsFixtures.text(s, ManageForm.STATUS).find("is not a favourite") != null;
+ s = ManageFixtures.send(vault, github, ManageAction.FAVORITE);
+ SettingsFixtures.run(vault, s);
+ var marked = (vault.header(github) as Entry).favorite;
+ // (The same form again would be a repeat; mark another entry directly.)
+ var vpn = vault.ids()[1];
+ var e = vault.open(vpn);
+ e.favorite = true;
+ vault.update(e);
+ e.wipeSecrets();
+ s = ManageFixtures.send(vault, vpn, ManageAction.FAVORITE);
+ SettingsFixtures.run(vault, s);
+ var already = SettingsFixtures.text(s, ManageForm.STATUS).find("already a favourite") != null;
+ var bank = ManageFixtures.addLocked(vault, "Bank");
+ vault.lock();
+ s = ManageFixtures.send(vault, bank, ManageAction.FAVORITE);
+ SettingsFixtures.run(vault, s);
+ var waits = ChangeQueue.count(vault) == 1 && !(vault.anyHeader(bank) as Entry).favorite;
+ var a = new ManageAction();
+ a.id = bank;
+ a.action = ManageAction.UNFAVORITE;
+ var parsed = ManageAction.fromBytes(a.toBytes());
+ var ok = notYet && marked && already && waits && parsed != null && parsed.action == ManageAction.UNFAVORITE
+ && a.describe().equals("Remove favourite: " + bank) && ManageAction.NAMES.size() == ManageAction.UNFAVORITE + 1;
+ if (!ok) {
+ logger.error("notYet " + notYet + " marked " + marked + " already " + already + " waits " + waits + " queue "
+ + ChangeQueue.count(vault) + " describe '" + a.describe() + "'");
+ }
+ return ok;
+}
+
+(:test)
+function glanceGroupsDigits(logger as Test.Logger) as Boolean {
+ return GlanceCode.grouped("123456").equals("123 456") && GlanceCode.grouped("1234567").equals("1234 567")
+ && GlanceCode.grouped("12345678").equals("1234 5678") && GlanceCode.grouped("VK4T9").equals("VK4T9");
+}