tor-browser

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

test_hmac_error.js (8389B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 const { Service } = ChromeUtils.importESModule(
      5  "resource://services-sync/service.sys.mjs"
      6 );
      7 
      8 // Track HMAC error counts.
      9 var hmacErrorCount = 0;
     10 (function () {
     11  let hHE = Service.handleHMACEvent;
     12  Service.handleHMACEvent = async function () {
     13    hmacErrorCount++;
     14    return hHE.call(Service);
     15  };
     16 })();
     17 
     18 async function shared_setup() {
     19  enableValidationPrefs();
     20  syncTestLogging();
     21 
     22  hmacErrorCount = 0;
     23 
     24  let clientsEngine = Service.clientsEngine;
     25  let clientsSyncID = await clientsEngine.resetLocalSyncID();
     26 
     27  // Make sure RotaryEngine is the only one we sync.
     28  let { engine, syncID, tracker } = await registerRotaryEngine();
     29  await engine.setLastSync(123); // Needs to be non-zero so that tracker is queried.
     30  engine._store.items = {
     31    flying: "LNER Class A3 4472",
     32    scotsman: "Flying Scotsman",
     33  };
     34  await tracker.addChangedID("scotsman", 0);
     35  Assert.equal(1, Service.engineManager.getEnabled().length);
     36 
     37  let engines = {
     38    rotary: { version: engine.version, syncID },
     39    clients: { version: clientsEngine.version, syncID: clientsSyncID },
     40  };
     41 
     42  // Common server objects.
     43  let global = new ServerWBO("global", { engines });
     44  let keysWBO = new ServerWBO("keys");
     45  let rotaryColl = new ServerCollection({}, true);
     46  let clientsColl = new ServerCollection({}, true);
     47 
     48  return [engine, rotaryColl, clientsColl, keysWBO, global, tracker];
     49 }
     50 
     51 add_task(async function hmac_error_during_404() {
     52  _("Attempt to replicate the HMAC error setup.");
     53  let [engine, rotaryColl, clientsColl, keysWBO, global, tracker] =
     54    await shared_setup();
     55 
     56  // Hand out 404s for crypto/keys.
     57  let keysHandler = keysWBO.handler();
     58  let key404Counter = 0;
     59  let keys404Handler = function (request, response) {
     60    if (key404Counter > 0) {
     61      let body = "Not Found";
     62      response.setStatusLine(request.httpVersion, 404, body);
     63      response.bodyOutputStream.write(body, body.length);
     64      key404Counter--;
     65      return;
     66    }
     67    keysHandler(request, response);
     68  };
     69 
     70  let collectionsHelper = track_collections_helper();
     71  let upd = collectionsHelper.with_updated_collection;
     72  let handlers = {
     73    "/1.1/foo/info/collections": collectionsHelper.handler,
     74    "/1.1/foo/storage/meta/global": upd("meta", global.handler()),
     75    "/1.1/foo/storage/crypto/keys": upd("crypto", keys404Handler),
     76    "/1.1/foo/storage/clients": upd("clients", clientsColl.handler()),
     77    "/1.1/foo/storage/rotary": upd("rotary", rotaryColl.handler()),
     78  };
     79 
     80  let server = sync_httpd_setup(handlers);
     81  // Do not instantiate SyncTestingInfrastructure; we need real crypto.
     82  await configureIdentity({ username: "foo" }, server);
     83  await Service.login();
     84 
     85  try {
     86    _("Syncing.");
     87    await sync_and_validate_telem();
     88 
     89    _(
     90      "Partially resetting client, as if after a restart, and forcing redownload."
     91    );
     92    Service.collectionKeys.clear();
     93    await engine.setLastSync(0); // So that we redownload records.
     94    key404Counter = 1;
     95    _("---------------------------");
     96    await sync_and_validate_telem();
     97    _("---------------------------");
     98 
     99    // Two rotary items, one client record... no errors.
    100    Assert.equal(hmacErrorCount, 0);
    101  } finally {
    102    await tracker.clearChangedIDs();
    103    await Service.engineManager.unregister(engine);
    104    for (const pref of Svc.PrefBranch.getChildList("")) {
    105      Svc.PrefBranch.clearUserPref(pref);
    106    }
    107    Service.recordManager.clearCache();
    108    await promiseStopServer(server);
    109  }
    110 });
    111 
    112 add_task(async function hmac_error_during_node_reassignment() {
    113  _("Attempt to replicate an HMAC error during node reassignment.");
    114  let [engine, rotaryColl, clientsColl, keysWBO, global, tracker] =
    115    await shared_setup();
    116 
    117  let collectionsHelper = track_collections_helper();
    118  let upd = collectionsHelper.with_updated_collection;
    119 
    120  // We'll provide a 401 mid-way through the sync. This function
    121  // simulates shifting to a node which has no data.
    122  function on401() {
    123    _("Deleting server data...");
    124    global.delete();
    125    rotaryColl.delete();
    126    keysWBO.delete();
    127    clientsColl.delete();
    128    delete collectionsHelper.collections.rotary;
    129    delete collectionsHelper.collections.crypto;
    130    delete collectionsHelper.collections.clients;
    131    _("Deleted server data.");
    132  }
    133 
    134  let should401 = false;
    135  function upd401(coll, handler) {
    136    return function (request, response) {
    137      if (should401 && request.method != "DELETE") {
    138        on401();
    139        should401 = false;
    140        let body = '"reassigned!"';
    141        response.setStatusLine(request.httpVersion, 401, "Node reassignment.");
    142        response.bodyOutputStream.write(body, body.length);
    143        return;
    144      }
    145      handler(request, response);
    146    };
    147  }
    148 
    149  let handlers = {
    150    "/1.1/foo/info/collections": collectionsHelper.handler,
    151    "/1.1/foo/storage/meta/global": upd("meta", global.handler()),
    152    "/1.1/foo/storage/crypto/keys": upd("crypto", keysWBO.handler()),
    153    "/1.1/foo/storage/clients": upd401("clients", clientsColl.handler()),
    154    "/1.1/foo/storage/rotary": upd("rotary", rotaryColl.handler()),
    155  };
    156 
    157  let server = sync_httpd_setup(handlers);
    158  // Do not instantiate SyncTestingInfrastructure; we need real crypto.
    159  await configureIdentity({ username: "foo" }, server);
    160 
    161  _("Syncing.");
    162  // First hit of clients will 401. This will happen after meta/global and
    163  // keys -- i.e., in the middle of the sync, but before RotaryEngine.
    164  should401 = true;
    165 
    166  // Use observers to perform actions when our sync finishes.
    167  // This allows us to observe the automatic next-tick sync that occurs after
    168  // an abort.
    169  function onSyncError() {
    170    do_throw("Should not get a sync error!");
    171  }
    172  let onSyncFinished = function () {};
    173  let obs = {
    174    observe: function observe(subject, topic) {
    175      switch (topic) {
    176        case "weave:service:sync:error":
    177          onSyncError();
    178          break;
    179        case "weave:service:sync:finish":
    180          onSyncFinished();
    181          break;
    182      }
    183    },
    184  };
    185 
    186  Svc.Obs.add("weave:service:sync:finish", obs);
    187  Svc.Obs.add("weave:service:sync:error", obs);
    188 
    189  // This kicks off the actual test. Split into a function here to allow this
    190  // source file to broadly follow actual execution order.
    191  async function onwards() {
    192    _("== Invoking first sync.");
    193    await Service.sync();
    194    _("We should not simultaneously have data but no keys on the server.");
    195    let hasData = rotaryColl.wbo("flying") || rotaryColl.wbo("scotsman");
    196    let hasKeys = keysWBO.modified;
    197 
    198    _("We correctly handle 401s by aborting the sync and starting again.");
    199    Assert.equal(!hasData, !hasKeys);
    200 
    201    _("Be prepared for the second (automatic) sync...");
    202  }
    203 
    204  _("Make sure that syncing again causes recovery.");
    205  let callbacksPromise = new Promise(resolve => {
    206    onSyncFinished = function () {
    207      _("== First sync done.");
    208      _("---------------------------");
    209      onSyncFinished = function () {
    210        _("== Second (automatic) sync done.");
    211        let hasData = rotaryColl.wbo("flying") || rotaryColl.wbo("scotsman");
    212        let hasKeys = keysWBO.modified;
    213        Assert.equal(!hasData, !hasKeys);
    214 
    215        // Kick off another sync. Can't just call it, because we're inside the
    216        // lock...
    217        (async () => {
    218          await Async.promiseYield();
    219          _("Now a fresh sync will get no HMAC errors.");
    220          _(
    221            "Partially resetting client, as if after a restart, and forcing redownload."
    222          );
    223          Service.collectionKeys.clear();
    224          await engine.setLastSync(0);
    225          hmacErrorCount = 0;
    226 
    227          onSyncFinished = async function () {
    228            // Two rotary items, one client record... no errors.
    229            Assert.equal(hmacErrorCount, 0);
    230 
    231            Svc.Obs.remove("weave:service:sync:finish", obs);
    232            Svc.Obs.remove("weave:service:sync:error", obs);
    233 
    234            await tracker.clearChangedIDs();
    235            await Service.engineManager.unregister(engine);
    236            for (const pref of Svc.PrefBranch.getChildList("")) {
    237              Svc.PrefBranch.clearUserPref(pref);
    238            }
    239            Service.recordManager.clearCache();
    240            server.stop(resolve);
    241          };
    242 
    243          Service.sync();
    244        })().catch(console.error);
    245      };
    246    };
    247  });
    248  await onwards();
    249  await callbacksPromise;
    250 });