// Author: cirakg & claude ai // Summary: Debug-only fake entries (and, in UI tests, a fake import and filled-in forms via settings); release builds get empty stubs. // // All data is fabricated: the secrets are the RFC 4226/6238 test keys and the // names are made up. The UI-test build (source/util/TestMode.mc) wipes the // store and loads these at every start; debug builds offer "Load demo // entries" in the menu. FIXTURE_PIN is the PIN of the UI-test store. // // FIXTURE_WRAP is that PIN's wrap of FIXTURE_MASTER_KEY, precomputed by // tools/gen_fixture_wrap.py (fake salt, IV and key): deriving the PIN key on // the watch in one event trips the watchdog, so the fixture stores the // result, with the 4-digit PIN marker (KeyManager.PIN_LENGTH_KEY). // Unlocking it with the app's own code also cross-checks the wrap format // against the Python implementation (test/storage/PinTest.mc). // // The extra UI-test scenario (TestMode.SCENARIO_EXTRA, uitest-extra.jungle) // loads loadExtra() instead: FIXTURE_LEGACY_WRAP, the same master key under // the 6-digit PIN FIXTURE_LEGACY_PIN without the marker (a PIN from before // the 4-digit rule), and the entries and forms its flow needs. import Toybox.Application; import Toybox.Lang; (:debug) module Fixtures { const FIXTURE_PIN = "1234"; const FIXTURE_WRAP = "101112131415161718191a1b1c1d1e1f000007d001202122232425262728292a2b2c2d2e2f133549536635b7a7ae3add45a59c42953d4c666538390a99305164dba9c99961e7148e1c561b6cfc70cfde7f9e3e59179464ce65bf91dd08807ff8a52027d77c90c2b692ca22fe4cbd3fb20ffb440137"; // lint: allow-literal (fake fixture) const FIXTURE_MASTER_KEY = "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"; // lint: allow-literal (fake fixture) const FIXTURE_LEGACY_PIN = "123456"; const FIXTURE_LEGACY_WRAP = "101112131415161718191a1b1c1d1e1f000007d001202122232425262728292a2b2c2d2e2f857b961d4854e06740f602627bfb952efaac5383e800768221bf3a7b292ee881e2ce2f76b9dd744b2ac32322473df351929bc6dfcbfc2941527ae8b90614a8a6a6bd0f72d33161bafd16b1039caa670c"; // lint: allow-literal (fake fixture) // Erase all app storage (UI-test build only). function resetStore() as Void { Application.Storage.clearValues(); } // Add the demo entries. In the UI-test build this also sets FIXTURE_PIN // so one entry can be locked; otherwise locked demo entries go to the // pending queue until a PIN exists. function load(vault as Vault) as Void { var extra = TestMode.ON && TestMode.SCENARIO == TestMode.SCENARIO_EXTRA; if (TestMode.ON) { // The extra scenario's screens use the light theme, the main // one's the dark theme. Properties outlive a launch in the // simulator, so both set it. Application.Properties.setValue("optTheme", extra ? Options.THEME_LIGHT : Options.THEME_DARK); AppState.options.load(); } if (extra) { loadExtra(vault); return; } if (TestMode.ON && !vault.hasPin()) { Application.Storage.setValue(KeyManager.WRAP_KEY, Bytes.fromHex(FIXTURE_WRAP)); Application.Storage.setValue(KeyManager.PIN_LENGTH_KEY, KeyManager.PIN_DIGITS); vault.session.unlock(Bytes.fromHex(FIXTURE_MASTER_KEY)); } var rfcKey = "12345678901234567890"; // lint: allow-literal (RFC 4226 test key) var github = otp("GitHub", "alice@example.com", "Work", Entry.OTP_TOTP, Hashes.SHA1, 6, 30, rfcKey, false); github.favorite = true; // the glance code (UI test: optGlanceCode is on) vault.add(github); vault.add(otp("Google", "bob@example.com", "Personal", Entry.OTP_TOTP, Hashes.SHA256, 8, 30, "12345678901234567890123456789012", false)); // lint: allow-literal (RFC 6238 test key) vault.add(otp("Bank", "carol", "Finance", Entry.OTP_TOTP, Hashes.SHA512, 6, 30, "1234567890123456789012345678901234567890123456789012345678901234", true)); // lint: allow-literal (RFC 6238 test key) vault.add(otp("VPN", "dave", "Work", Entry.OTP_HOTP, Hashes.SHA1, 6, 30, rfcKey, false)); vault.add(otp("Steam", "erin", "Games", Entry.OTP_STEAM, Hashes.SHA1, 5, 30, rfcKey, false)); vault.add(otp("Library", "frank", "Personal", Entry.OTP_TOTP, Hashes.SHA1, 7, 60, rfcKey, false)); var card = new Entry(); card.kind = Entry.KIND_CODE; card.type = Codes.EAN13; card.name = "Gym card"; card.group = "Personal"; card.payload = Bytes.fromUtf8("590123412345"); // fake; the check digit 7 is added card.options = Codes.DEFAULT_OPTIONS; vault.add(card); var qr = new Entry(); qr.kind = Entry.KIND_CODE; qr.type = Codes.QR; qr.name = "Member QR"; qr.group = "Personal"; // Fake data; with the digit run in numeric mode it is a version 6 QR. qr.payload = Bytes.fromUtf8("https://example.com/member/fake-0042?name=Alice%20Example&card=0000111122223333&valid=2027-12-31&level=gold"); qr.options = Codes.DEFAULT_OPTIONS; vault.add(qr); vault.add(code("Parcel", Codes.DATAMATRIX, "PARCEL-FAKE-0042-7731")); vault.add(code("Ticket", Codes.PDF417, "Boarding pass FAKE 0042 seat 12A")); vault.add(code("Rail pass", Codes.AZTEC, "RAIL-FAKE-0042 Zone 1-3")); vault.lock(); if (TestMode.ON) { startImport(); sendForms(vault); } } // The extra UI-test scenario (README 17.4; tools/ui_flow.py // run_extra_flow): the legacy PIN, a locked TOTP, the symbologies the // main fixture lacks (UPC-A, Code 39), an EAN-13 whose check digit is // wrong (the code error screen) and a QR large enough that its encoding // progress shows for a while. The Manage form asks for "Require PIN" on // the UPC-A card while the watch is locked, so it waits for the next // PIN; the glance option is off, so the glance shows the entry count. // Its screens use the light theme (set in load()). function loadExtra(vault as Vault) as Void { Application.Storage.setValue(KeyManager.WRAP_KEY, Bytes.fromHex(FIXTURE_LEGACY_WRAP)); vault.session.unlock(Bytes.fromHex(FIXTURE_MASTER_KEY)); var rfcKey = "12345678901234567890"; // lint: allow-literal (RFC 4226 test key) vault.add(otp("GitHub", "alice@example.com", "Work", Entry.OTP_TOTP, Hashes.SHA1, 6, 30, rfcKey, false)); vault.add(otp("Bank", "carol", "Finance", Entry.OTP_TOTP, Hashes.SHA512, 6, 30, "1234567890123456789012345678901234567890123456789012345678901234", true)); // lint: allow-literal (RFC 6238 test key) vault.add(code("Shop card", Codes.UPCA, "036000291452")); // fake; a valid UPC-A check digit vault.add(code("Badge", Codes.CODE39, "FAKE-39")); vault.add(code("Broken card", Codes.EAN13, "5901234123458")); // the check digit should be 7 // Fake text of 199 characters: a version 10 QR in byte mode, which // still fits the smallest screen at 2 px per module. var text = "Fake event ticket 0042 for the UI test."; while (text.length() < 160) { text += " Row 12, seat 34, gate b, valid all day."; } vault.add(code("Big QR", Codes.QR, text)); vault.lock(); Application.Properties.setValue("optGlanceCode", false); vault.glanceCode = false; Application.Properties.setValue("impApply", false); Application.Properties.setValue("otpAdd", false); Application.Properties.setValue("codeAdd", false); var ids = vault.ids(); Application.Properties.setValue("mgId", ids[2]); // Shop card Application.Properties.setValue("mgAction", ManageAction.LOCK); Application.Properties.setValue("mgApply", true); } // UI-test build: the glance option, and the other forms, filled in as // the phone would (README 16, M7 end-to-end): Add OTP (a TOTP whose code the screenshot shows), // Add code (a Code 128 card whose screenshot must decode) and Manage // entry (Bank's group renamed: Bank is locked, so the change waits for // the PIN, which the UI flow confirms from the menu). They are applied // after the import, in README 10.3 order, so the new entries end the // list. function sendForms(vault as Vault) as Void { // The glance shows GitHub's code after the flow (the About scan // seals it for the glance). Application.Properties.setValue("optGlanceCode", true); vault.glanceCode = true; Application.Properties.setValue("otpType", Entry.OTP_TOTP); Application.Properties.setValue("otpIssuer", "Mailbox"); Application.Properties.setValue("otpAccount", "fixture@example.com"); Application.Properties.setValue("otpSecret", "JBSW Y3DP EHPK 3PXP"); // lint: allow-literal (the classic "Hello!" demo key) Application.Properties.setValue("otpAlgorithm", Hashes.SHA1); Application.Properties.setValue("otpDigits", 6); Application.Properties.setValue("otpPeriod", 30); Application.Properties.setValue("otpGroup", "Personal"); Application.Properties.setValue("otpAdd", true); Application.Properties.setValue("codeName", "Locker"); Application.Properties.setValue("codeSymbology", Codes.CODE128); Application.Properties.setValue("codePayload", "FAKE-0042"); Application.Properties.setValue("codeGroup", "Personal"); Application.Properties.setValue("codeAdd", true); var ids = vault.ids(); Application.Properties.setValue("mgId", ids[2]); // Bank Application.Properties.setValue("mgAction", ManageAction.EDIT); Application.Properties.setValue("mgGroup", "Money"); Application.Properties.setValue("mgApply", true); } // UI-test build: an Aegis import of 100 entries through the settings // form, the same path as the phone: 97 new, one duplicate of GitHub, one // error and one skipped type. The results screen opens when it's done. function startImport() as Void { var rfcKey = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ"; // lint: allow-literal (RFC 4226 test key, = GitHub's) var proton = "KRSXG5BAKNSWG4TFOQQEWZLZEBEGK4TF"; // lint: allow-literal (fake: Base32 of "Test Secret Key Here") var json = "{\"version\": 1, \"header\": {\"slots\": null, \"params\": null}, \"db\": {\"version\": 3, \"entries\": [" + aegisEntry("totp", "Proton", "zoe@example.com", proton, 6) + "," + aegisEntry("totp", "GitHub copy", "alice@example.com", rfcKey, 6) + "," + aegisEntry("totp", "Broken", "x", proton, 9) + "," + aegisEntry("motp", "Legacy", "y", rfcKey, 6); // 96 more, so the flow imports 100 entries through the paced job. // Fake 16-character Base32 secrets built from alphabet letters (cheap: // Base32-encoding 96 secrets in this one event would trip the watchdog // on the Forerunner 255S). var abc = Base32.ALPHABET; for (var i = 0; i < 96; i++) { var secret = "MFRGGZDF" + abc.substring(i / 32, i / 32 + 1) + abc.substring(i % 32, i % 32 + 1) + "MZTWQ2LK"; json += "," + aegisEntry("totp", "Site " + i, "user" + i + "@example.com", secret, 6); } json += "], \"groups\": []}}"; Application.Properties.setValue("impText1", json); Application.Properties.setValue("impMode", 0); Application.Properties.setValue("impLocked", false); Application.Properties.setValue("impApply", true); } function aegisEntry(type as String, issuer as String, name as String, secret as String, digits as Number) as String { return "{\"type\": \"" + type + "\", \"name\": \"" + name + "\", \"issuer\": \"" + issuer + "\", \"info\": {\"secret\": \"" + secret + "\", \"algo\": \"SHA1\", \"digits\": " + digits + ", \"period\": 30}}"; } function code(name as String, symbology as Number, payload as String) as Entry { var e = new Entry(); e.kind = Entry.KIND_CODE; e.type = symbology; e.name = name; e.group = "Personal"; e.payload = Bytes.fromUtf8(payload); e.options = Codes.DEFAULT_OPTIONS; return e; } function otp(name as String, account as String, group as String, type as Number, algorithm as Number, digits as Number, period as Number, secret as String, locked as Boolean) as Entry { var e = new Entry(); e.name = name; e.account = account; e.group = group; e.type = type; e.algorithm = algorithm; e.digits = digits < Otp.MIN_DIGITS ? Otp.MIN_DIGITS : digits; e.period = period; e.secret = Bytes.fromUtf8(secret); e.locked = locked; e.counter = type == Entry.OTP_HOTP ? 5l : 0l; e.created = 1700000000l; return e; } } (:release) module Fixtures { function resetStore() as Void { } function load(vault as Vault) as Void { } }