services/sync/tests/unit/test_corrupt_keys.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/services/sync/tests/unit/test_corrupt_keys.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,227 @@
     1.4 +/* Any copyright is dedicated to the Public Domain.
     1.5 +   http://creativecommons.org/publicdomain/zero/1.0/ */
     1.6 +
     1.7 +Cu.import("resource://gre/modules/PlacesUtils.jsm");
     1.8 +Cu.import("resource://gre/modules/Log.jsm");
     1.9 +Cu.import("resource://services-sync/constants.js");
    1.10 +Cu.import("resource://services-sync/engines.js");
    1.11 +Cu.import("resource://services-sync/engines/tabs.js");
    1.12 +Cu.import("resource://services-sync/engines/history.js");
    1.13 +Cu.import("resource://services-sync/record.js");
    1.14 +Cu.import("resource://services-sync/service.js");
    1.15 +Cu.import("resource://services-sync/status.js");
    1.16 +Cu.import("resource://services-sync/util.js");
    1.17 +Cu.import("resource://testing-common/services/sync/utils.js");
    1.18 +Cu.import("resource://gre/modules/Promise.jsm");
    1.19 +
    1.20 +add_task(function test_locally_changed_keys() {
    1.21 +  let passphrase = "abcdeabcdeabcdeabcdeabcdea";
    1.22 +
    1.23 +  let hmacErrorCount = 0;
    1.24 +  function counting(f) {
    1.25 +    return function() {
    1.26 +      hmacErrorCount++;
    1.27 +      return f.call(this);
    1.28 +    };
    1.29 +  }
    1.30 +
    1.31 +  Service.handleHMACEvent = counting(Service.handleHMACEvent);
    1.32 +
    1.33 +  let server  = new SyncServer();
    1.34 +  let johndoe = server.registerUser("johndoe", "password");
    1.35 +  johndoe.createContents({
    1.36 +    meta: {},
    1.37 +    crypto: {},
    1.38 +    clients: {}
    1.39 +  });
    1.40 +  server.start();
    1.41 +
    1.42 +  try {
    1.43 +    Svc.Prefs.set("registerEngines", "Tab");
    1.44 +    _("Set up some tabs.");
    1.45 +    let myTabs =
    1.46 +      {windows: [{tabs: [{index: 1,
    1.47 +                          entries: [{
    1.48 +                            url: "http://foo.com/",
    1.49 +                            title: "Title"
    1.50 +                          }],
    1.51 +                          attributes: {
    1.52 +                            image: "image"
    1.53 +                          }
    1.54 +                          }]}]};
    1.55 +    delete Svc.Session;
    1.56 +    Svc.Session = {
    1.57 +      getBrowserState: function () JSON.stringify(myTabs)
    1.58 +    };
    1.59 +
    1.60 +    setBasicCredentials("johndoe", "password", passphrase);
    1.61 +    Service.serverURL = server.baseURI;
    1.62 +    Service.clusterURL = server.baseURI;
    1.63 +
    1.64 +    Service.engineManager.register(HistoryEngine);
    1.65 +
    1.66 +    function corrupt_local_keys() {
    1.67 +      Service.collectionKeys._default.keyPair = [Svc.Crypto.generateRandomKey(),
    1.68 +                                                 Svc.Crypto.generateRandomKey()];
    1.69 +    }
    1.70 +
    1.71 +    _("Setting meta.");
    1.72 +
    1.73 +    // Bump version on the server.
    1.74 +    let m = new WBORecord("meta", "global");
    1.75 +    m.payload = {"syncID": "foooooooooooooooooooooooooo",
    1.76 +                 "storageVersion": STORAGE_VERSION};
    1.77 +    m.upload(Service.resource(Service.metaURL));
    1.78 +
    1.79 +    _("New meta/global: " + JSON.stringify(johndoe.collection("meta").wbo("global")));
    1.80 +
    1.81 +    // Upload keys.
    1.82 +    generateNewKeys(Service.collectionKeys);
    1.83 +    let serverKeys = Service.collectionKeys.asWBO("crypto", "keys");
    1.84 +    serverKeys.encrypt(Service.identity.syncKeyBundle);
    1.85 +    do_check_true(serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success);
    1.86 +
    1.87 +    // Check that login works.
    1.88 +    do_check_true(Service.login("johndoe", "ilovejane", passphrase));
    1.89 +    do_check_true(Service.isLoggedIn);
    1.90 +
    1.91 +    // Sync should upload records.
    1.92 +    Service.sync();
    1.93 +
    1.94 +    // Tabs exist.
    1.95 +    _("Tabs modified: " + johndoe.modified("tabs"));
    1.96 +    do_check_true(johndoe.modified("tabs") > 0);
    1.97 +
    1.98 +    let coll_modified = Service.collectionKeys.lastModified;
    1.99 +
   1.100 +    // Let's create some server side history records.
   1.101 +    let liveKeys = Service.collectionKeys.keyForCollection("history");
   1.102 +    _("Keys now: " + liveKeys.keyPair);
   1.103 +    let visitType = Ci.nsINavHistoryService.TRANSITION_LINK;
   1.104 +    let history   = johndoe.createCollection("history");
   1.105 +    for (let i = 0; i < 5; i++) {
   1.106 +      let id = 'record-no--' + i;
   1.107 +      let modified = Date.now()/1000 - 60*(i+10);
   1.108 +
   1.109 +      let w = new CryptoWrapper("history", "id");
   1.110 +      w.cleartext = {
   1.111 +        id: id,
   1.112 +        histUri: "http://foo/bar?" + id,
   1.113 +        title: id,
   1.114 +        sortindex: i,
   1.115 +        visits: [{date: (modified - 5) * 1000000, type: visitType}],
   1.116 +        deleted: false};
   1.117 +      w.encrypt(liveKeys);
   1.118 +
   1.119 +      let payload = {ciphertext: w.ciphertext,
   1.120 +                     IV:         w.IV,
   1.121 +                     hmac:       w.hmac};
   1.122 +      history.insert(id, payload, modified);
   1.123 +    }
   1.124 +
   1.125 +    history.timestamp = Date.now() / 1000;
   1.126 +    let old_key_time = johndoe.modified("crypto");
   1.127 +    _("Old key time: " + old_key_time);
   1.128 +
   1.129 +    // Check that we can decrypt one.
   1.130 +    let rec = new CryptoWrapper("history", "record-no--0");
   1.131 +    rec.fetch(Service.resource(Service.storageURL + "history/record-no--0"));
   1.132 +    _(JSON.stringify(rec));
   1.133 +    do_check_true(!!rec.decrypt(liveKeys));
   1.134 +
   1.135 +    do_check_eq(hmacErrorCount, 0);
   1.136 +
   1.137 +    // Fill local key cache with bad data.
   1.138 +    corrupt_local_keys();
   1.139 +    _("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair);
   1.140 +
   1.141 +    do_check_eq(hmacErrorCount, 0);
   1.142 +
   1.143 +    _("HMAC error count: " + hmacErrorCount);
   1.144 +    // Now syncing should succeed, after one HMAC error.
   1.145 +    Service.sync();
   1.146 +    do_check_eq(hmacErrorCount, 1);
   1.147 +    _("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair);
   1.148 +
   1.149 +    // And look! We downloaded history!
   1.150 +    let store = Service.engineManager.get("history")._store;
   1.151 +    do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--0"));
   1.152 +    do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--1"));
   1.153 +    do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--2"));
   1.154 +    do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--3"));
   1.155 +    do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--4"));
   1.156 +    do_check_eq(hmacErrorCount, 1);
   1.157 +
   1.158 +    _("Busting some new server values.");
   1.159 +    // Now what happens if we corrupt the HMAC on the server?
   1.160 +    for (let i = 5; i < 10; i++) {
   1.161 +      let id = 'record-no--' + i;
   1.162 +      let modified = 1 + (Date.now() / 1000);
   1.163 +
   1.164 +      let w = new CryptoWrapper("history", "id");
   1.165 +      w.cleartext = {
   1.166 +        id: id,
   1.167 +        histUri: "http://foo/bar?" + id,
   1.168 +        title: id,
   1.169 +        sortindex: i,
   1.170 +        visits: [{date: (modified - 5 ) * 1000000, type: visitType}],
   1.171 +        deleted: false};
   1.172 +      w.encrypt(Service.collectionKeys.keyForCollection("history"));
   1.173 +      w.hmac = w.hmac.toUpperCase();
   1.174 +
   1.175 +      let payload = {ciphertext: w.ciphertext,
   1.176 +                     IV:         w.IV,
   1.177 +                     hmac:       w.hmac};
   1.178 +      history.insert(id, payload, modified);
   1.179 +    }
   1.180 +    history.timestamp = Date.now() / 1000;
   1.181 +
   1.182 +    _("Server key time hasn't changed.");
   1.183 +    do_check_eq(johndoe.modified("crypto"), old_key_time);
   1.184 +
   1.185 +    _("Resetting HMAC error timer.");
   1.186 +    Service.lastHMACEvent = 0;
   1.187 +
   1.188 +    _("Syncing...");
   1.189 +    Service.sync();
   1.190 +    _("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair);
   1.191 +    _("Server keys have been updated, and we skipped over 5 more HMAC errors without adjusting history.");
   1.192 +    do_check_true(johndoe.modified("crypto") > old_key_time);
   1.193 +    do_check_eq(hmacErrorCount, 6);
   1.194 +    do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--5"));
   1.195 +    do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--6"));
   1.196 +    do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--7"));
   1.197 +    do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--8"));
   1.198 +    do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--9"));
   1.199 +  } finally {
   1.200 +    Svc.Prefs.resetBranch("");
   1.201 +    let deferred = Promise.defer();
   1.202 +    server.stop(deferred.resolve);
   1.203 +    yield deferred.promise;
   1.204 +  }
   1.205 +});
   1.206 +
   1.207 +function run_test() {
   1.208 +  let logger = Log.repository.rootLogger;
   1.209 +  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
   1.210 +
   1.211 +  ensureLegacyIdentityManager();
   1.212 +
   1.213 +  run_next_test();
   1.214 +}
   1.215 +
   1.216 +/**
   1.217 + * Asynchronously check a url is visited.
   1.218 + * @param url the url
   1.219 + * @return {Promise}
   1.220 + * @resolves When the check has been added successfully.
   1.221 + * @rejects JavaScript exception.
   1.222 + */
   1.223 +function promiseIsURIVisited(url) {
   1.224 +  let deferred = Promise.defer();
   1.225 +  PlacesUtils.asyncHistory.isURIVisited(Utils.makeURI(url), function(aURI, aIsVisited) {
   1.226 +    deferred.resolve(aIsVisited);
   1.227 +  });
   1.228 +
   1.229 +  return deferred.promise;
   1.230 +}

mercurial