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 +}