summaryrefslogtreecommitdiff
path: root/source/app/Fixtures.mc
blob: 8ed35c9c04d41362573d39de2f84bb3aab0c9757 (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
// 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 = 0;
        card.name = "Gym card";
        card.group = "Personal";
        card.payload = Bytes.fromUtf8("0000-1111-2222");
        vault.add(card);
        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 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 {
    }
}