diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/crypto/KdfAesTest.mc | 2 | ||||
| -rw-r--r-- | test/ui/GlanceCodeTest.mc | 151 |
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"); +} |
