summaryrefslogtreecommitdiff
path: root/source/app/Fixtures.mc
blob: 0d6b683c5088e38c1b02a8691cc435e7857b6206 (plain) (blame)
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
// Author:  cirakg & claude ai
// Summary: Debug-only fake entries (and, in UI tests, a fake import via the settings form); 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)
        vault.add(otp("GitHub", "alice@example.com", "Work", Entry.OTP_TOTP, Hashes.SHA1, 6, 30, rfcKey, false));
        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.lock();
        if (TestMode.ON) {
            startImport();
        }
    }

    // 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 {
    }
}