// 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. Unlocking it with the app's own code also cross-checks the wrap // format against the Python implementation (test/storage/PinTest.mc). import Toybox.Application; import Toybox.Lang; (:debug) module Fixtures { const FIXTURE_PIN = "123456"; const FIXTURE_WRAP = "101112131415161718191a1b1c1d1e1f000007d001202122232425262728292a2b2c2d2e2f857b961d4854e06740f602627bfb952efaac5383e800768221bf3a7b292ee881e2ce2f76b9dd744b2ac32322473df351929bc6dfcbfc2941527ae8b90614a8a6a6bd0f72d33161bafd16b1039caa670c"; // lint: allow-literal (fake fixture) const FIXTURE_MASTER_KEY = "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"; // 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 { if (TestMode.ON && !vault.hasPin()) { Application.Storage.setValue(KeyManager.WRAP_KEY, Bytes.fromHex(FIXTURE_WRAP)); 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); } } // 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 { } }