1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
|
// 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 {
}
}
|