tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_corrupt_keys.js (7431B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 const { Weave } = ChromeUtils.importESModule(
      5  "resource://services-sync/main.sys.mjs"
      6 );
      7 const { HistoryEngine } = ChromeUtils.importESModule(
      8  "resource://services-sync/engines/history.sys.mjs"
      9 );
     10 const { CryptoWrapper, WBORecord } = ChromeUtils.importESModule(
     11  "resource://services-sync/record.sys.mjs"
     12 );
     13 const { Service } = ChromeUtils.importESModule(
     14  "resource://services-sync/service.sys.mjs"
     15 );
     16 
     17 add_task(async function test_locally_changed_keys() {
     18  enableValidationPrefs();
     19 
     20  let hmacErrorCount = 0;
     21  function counting(f) {
     22    return async function () {
     23      hmacErrorCount++;
     24      return f.call(this);
     25    };
     26  }
     27 
     28  Service.handleHMACEvent = counting(Service.handleHMACEvent);
     29 
     30  let server = new SyncServer();
     31  let johndoe = server.registerUser("johndoe", "password");
     32  johndoe.createContents({
     33    meta: {},
     34    crypto: {},
     35    clients: {},
     36  });
     37  server.start();
     38 
     39  try {
     40    Svc.PrefBranch.setStringPref("registerEngines", "Tab");
     41 
     42    await configureIdentity({ username: "johndoe" }, server);
     43    // We aren't doing a .login yet, so fudge the cluster URL.
     44    Service.clusterURL = Service.identity._token.endpoint;
     45 
     46    await Service.engineManager.register(HistoryEngine);
     47    // Disable addon sync because AddonManager won't be initialized here.
     48    await Service.engineManager.unregister("addons");
     49    await Service.engineManager.unregister("extension-storage");
     50 
     51    async function corrupt_local_keys() {
     52      Service.collectionKeys._default.keyPair = [
     53        await Weave.Crypto.generateRandomKey(),
     54        await Weave.Crypto.generateRandomKey(),
     55      ];
     56    }
     57 
     58    _("Setting meta.");
     59 
     60    // Bump version on the server.
     61    let m = new WBORecord("meta", "global");
     62    m.payload = {
     63      syncID: "foooooooooooooooooooooooooo",
     64      storageVersion: STORAGE_VERSION,
     65    };
     66    await m.upload(Service.resource(Service.metaURL));
     67 
     68    _(
     69      "New meta/global: " +
     70        JSON.stringify(johndoe.collection("meta").wbo("global"))
     71    );
     72 
     73    // Upload keys.
     74    await generateNewKeys(Service.collectionKeys);
     75    let serverKeys = Service.collectionKeys.asWBO("crypto", "keys");
     76    await serverKeys.encrypt(Service.identity.syncKeyBundle);
     77    Assert.ok(
     78      (await serverKeys.upload(Service.resource(Service.cryptoKeysURL))).success
     79    );
     80 
     81    // Check that login works.
     82    Assert.ok(await Service.login());
     83    Assert.ok(Service.isLoggedIn);
     84 
     85    // Sync should upload records.
     86    await sync_and_validate_telem();
     87 
     88    // Tabs exist.
     89    _("Tabs modified: " + johndoe.modified("tabs"));
     90    Assert.greater(johndoe.modified("tabs"), 0);
     91 
     92    // Let's create some server side history records.
     93    let liveKeys = Service.collectionKeys.keyForCollection("history");
     94    _("Keys now: " + liveKeys.keyPair);
     95    let visitType = Ci.nsINavHistoryService.TRANSITION_LINK;
     96    let history = johndoe.createCollection("history");
     97    for (let i = 0; i < 5; i++) {
     98      let id = "record-no--" + i;
     99      let modified = Date.now() / 1000 - 60 * (i + 10);
    100 
    101      let w = new CryptoWrapper("history", "id");
    102      w.cleartext = {
    103        id,
    104        histUri: "http://foo/bar?" + id,
    105        title: id,
    106        sortindex: i,
    107        visits: [{ date: (modified - 5) * 1000000, type: visitType }],
    108        deleted: false,
    109      };
    110      await w.encrypt(liveKeys);
    111 
    112      let payload = { ciphertext: w.ciphertext, IV: w.IV, hmac: w.hmac };
    113      history.insert(id, payload, modified);
    114    }
    115 
    116    history.timestamp = Date.now() / 1000;
    117    let old_key_time = johndoe.modified("crypto");
    118    _("Old key time: " + old_key_time);
    119 
    120    // Check that we can decrypt one.
    121    let rec = new CryptoWrapper("history", "record-no--0");
    122    await rec.fetch(
    123      Service.resource(Service.storageURL + "history/record-no--0")
    124    );
    125    _(JSON.stringify(rec));
    126    Assert.ok(!!(await rec.decrypt(liveKeys)));
    127 
    128    Assert.equal(hmacErrorCount, 0);
    129 
    130    // Fill local key cache with bad data.
    131    await corrupt_local_keys();
    132    _(
    133      "Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair
    134    );
    135 
    136    Assert.equal(hmacErrorCount, 0);
    137 
    138    _("HMAC error count: " + hmacErrorCount);
    139    // Now syncing should succeed, after one HMAC error.
    140    await sync_and_validate_telem(ping => {
    141      Assert.equal(
    142        ping.engines.find(e => e.name == "history").incoming.applied,
    143        5
    144      );
    145    });
    146 
    147    Assert.equal(hmacErrorCount, 1);
    148    _(
    149      "Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair
    150    );
    151 
    152    // And look! We downloaded history!
    153    Assert.ok(
    154      await PlacesUtils.history.hasVisits("http://foo/bar?record-no--0")
    155    );
    156    Assert.ok(
    157      await PlacesUtils.history.hasVisits("http://foo/bar?record-no--1")
    158    );
    159    Assert.ok(
    160      await PlacesUtils.history.hasVisits("http://foo/bar?record-no--2")
    161    );
    162    Assert.ok(
    163      await PlacesUtils.history.hasVisits("http://foo/bar?record-no--3")
    164    );
    165    Assert.ok(
    166      await PlacesUtils.history.hasVisits("http://foo/bar?record-no--4")
    167    );
    168    Assert.equal(hmacErrorCount, 1);
    169 
    170    _("Busting some new server values.");
    171    // Now what happens if we corrupt the HMAC on the server?
    172    for (let i = 5; i < 10; i++) {
    173      let id = "record-no--" + i;
    174      let modified = 1 + Date.now() / 1000;
    175 
    176      let w = new CryptoWrapper("history", "id");
    177      w.cleartext = {
    178        id,
    179        histUri: "http://foo/bar?" + id,
    180        title: id,
    181        sortindex: i,
    182        visits: [{ date: (modified - 5) * 1000000, type: visitType }],
    183        deleted: false,
    184      };
    185      await w.encrypt(Service.collectionKeys.keyForCollection("history"));
    186      w.hmac = w.hmac.toUpperCase();
    187 
    188      let payload = { ciphertext: w.ciphertext, IV: w.IV, hmac: w.hmac };
    189      history.insert(id, payload, modified);
    190    }
    191    history.timestamp = Date.now() / 1000;
    192 
    193    _("Server key time hasn't changed.");
    194    Assert.equal(johndoe.modified("crypto"), old_key_time);
    195 
    196    _("Resetting HMAC error timer.");
    197    Service.lastHMACEvent = 0;
    198 
    199    _("Syncing...");
    200    await sync_and_validate_telem(ping => {
    201      Assert.equal(
    202        ping.engines.find(e => e.name == "history").incoming.failed,
    203        5
    204      );
    205    });
    206 
    207    _(
    208      "Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair
    209    );
    210    _(
    211      "Server keys have been updated, and we skipped over 5 more HMAC errors without adjusting history."
    212    );
    213    Assert.greater(johndoe.modified("crypto"), old_key_time);
    214    Assert.equal(hmacErrorCount, 6);
    215    Assert.equal(
    216      false,
    217      await PlacesUtils.history.hasVisits("http://foo/bar?record-no--5")
    218    );
    219    Assert.equal(
    220      false,
    221      await PlacesUtils.history.hasVisits("http://foo/bar?record-no--6")
    222    );
    223    Assert.equal(
    224      false,
    225      await PlacesUtils.history.hasVisits("http://foo/bar?record-no--7")
    226    );
    227    Assert.equal(
    228      false,
    229      await PlacesUtils.history.hasVisits("http://foo/bar?record-no--8")
    230    );
    231    Assert.equal(
    232      false,
    233      await PlacesUtils.history.hasVisits("http://foo/bar?record-no--9")
    234    );
    235  } finally {
    236    for (const pref of Svc.PrefBranch.getChildList("")) {
    237      Svc.PrefBranch.clearUserPref(pref);
    238    }
    239    await promiseStopServer(server);
    240  }
    241 });
    242 
    243 function run_test() {
    244  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
    245  validate_all_future_pings();
    246 
    247  run_next_test();
    248 }