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
|
// 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).
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)
// 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));
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);
}
}
// 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 {
}
}
|