services/sync/tests/unit/test_corrupt_keys.js

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:f34e8254330f
1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
3
4 Cu.import("resource://gre/modules/PlacesUtils.jsm");
5 Cu.import("resource://gre/modules/Log.jsm");
6 Cu.import("resource://services-sync/constants.js");
7 Cu.import("resource://services-sync/engines.js");
8 Cu.import("resource://services-sync/engines/tabs.js");
9 Cu.import("resource://services-sync/engines/history.js");
10 Cu.import("resource://services-sync/record.js");
11 Cu.import("resource://services-sync/service.js");
12 Cu.import("resource://services-sync/status.js");
13 Cu.import("resource://services-sync/util.js");
14 Cu.import("resource://testing-common/services/sync/utils.js");
15 Cu.import("resource://gre/modules/Promise.jsm");
16
17 add_task(function test_locally_changed_keys() {
18 let passphrase = "abcdeabcdeabcdeabcdeabcdea";
19
20 let hmacErrorCount = 0;
21 function counting(f) {
22 return 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.Prefs.set("registerEngines", "Tab");
41 _("Set up some tabs.");
42 let myTabs =
43 {windows: [{tabs: [{index: 1,
44 entries: [{
45 url: "http://foo.com/",
46 title: "Title"
47 }],
48 attributes: {
49 image: "image"
50 }
51 }]}]};
52 delete Svc.Session;
53 Svc.Session = {
54 getBrowserState: function () JSON.stringify(myTabs)
55 };
56
57 setBasicCredentials("johndoe", "password", passphrase);
58 Service.serverURL = server.baseURI;
59 Service.clusterURL = server.baseURI;
60
61 Service.engineManager.register(HistoryEngine);
62
63 function corrupt_local_keys() {
64 Service.collectionKeys._default.keyPair = [Svc.Crypto.generateRandomKey(),
65 Svc.Crypto.generateRandomKey()];
66 }
67
68 _("Setting meta.");
69
70 // Bump version on the server.
71 let m = new WBORecord("meta", "global");
72 m.payload = {"syncID": "foooooooooooooooooooooooooo",
73 "storageVersion": STORAGE_VERSION};
74 m.upload(Service.resource(Service.metaURL));
75
76 _("New meta/global: " + JSON.stringify(johndoe.collection("meta").wbo("global")));
77
78 // Upload keys.
79 generateNewKeys(Service.collectionKeys);
80 let serverKeys = Service.collectionKeys.asWBO("crypto", "keys");
81 serverKeys.encrypt(Service.identity.syncKeyBundle);
82 do_check_true(serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success);
83
84 // Check that login works.
85 do_check_true(Service.login("johndoe", "ilovejane", passphrase));
86 do_check_true(Service.isLoggedIn);
87
88 // Sync should upload records.
89 Service.sync();
90
91 // Tabs exist.
92 _("Tabs modified: " + johndoe.modified("tabs"));
93 do_check_true(johndoe.modified("tabs") > 0);
94
95 let coll_modified = Service.collectionKeys.lastModified;
96
97 // Let's create some server side history records.
98 let liveKeys = Service.collectionKeys.keyForCollection("history");
99 _("Keys now: " + liveKeys.keyPair);
100 let visitType = Ci.nsINavHistoryService.TRANSITION_LINK;
101 let history = johndoe.createCollection("history");
102 for (let i = 0; i < 5; i++) {
103 let id = 'record-no--' + i;
104 let modified = Date.now()/1000 - 60*(i+10);
105
106 let w = new CryptoWrapper("history", "id");
107 w.cleartext = {
108 id: id,
109 histUri: "http://foo/bar?" + id,
110 title: id,
111 sortindex: i,
112 visits: [{date: (modified - 5) * 1000000, type: visitType}],
113 deleted: false};
114 w.encrypt(liveKeys);
115
116 let payload = {ciphertext: w.ciphertext,
117 IV: w.IV,
118 hmac: w.hmac};
119 history.insert(id, payload, modified);
120 }
121
122 history.timestamp = Date.now() / 1000;
123 let old_key_time = johndoe.modified("crypto");
124 _("Old key time: " + old_key_time);
125
126 // Check that we can decrypt one.
127 let rec = new CryptoWrapper("history", "record-no--0");
128 rec.fetch(Service.resource(Service.storageURL + "history/record-no--0"));
129 _(JSON.stringify(rec));
130 do_check_true(!!rec.decrypt(liveKeys));
131
132 do_check_eq(hmacErrorCount, 0);
133
134 // Fill local key cache with bad data.
135 corrupt_local_keys();
136 _("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair);
137
138 do_check_eq(hmacErrorCount, 0);
139
140 _("HMAC error count: " + hmacErrorCount);
141 // Now syncing should succeed, after one HMAC error.
142 Service.sync();
143 do_check_eq(hmacErrorCount, 1);
144 _("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair);
145
146 // And look! We downloaded history!
147 let store = Service.engineManager.get("history")._store;
148 do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--0"));
149 do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--1"));
150 do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--2"));
151 do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--3"));
152 do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--4"));
153 do_check_eq(hmacErrorCount, 1);
154
155 _("Busting some new server values.");
156 // Now what happens if we corrupt the HMAC on the server?
157 for (let i = 5; i < 10; i++) {
158 let id = 'record-no--' + i;
159 let modified = 1 + (Date.now() / 1000);
160
161 let w = new CryptoWrapper("history", "id");
162 w.cleartext = {
163 id: id,
164 histUri: "http://foo/bar?" + id,
165 title: id,
166 sortindex: i,
167 visits: [{date: (modified - 5 ) * 1000000, type: visitType}],
168 deleted: false};
169 w.encrypt(Service.collectionKeys.keyForCollection("history"));
170 w.hmac = w.hmac.toUpperCase();
171
172 let payload = {ciphertext: w.ciphertext,
173 IV: w.IV,
174 hmac: w.hmac};
175 history.insert(id, payload, modified);
176 }
177 history.timestamp = Date.now() / 1000;
178
179 _("Server key time hasn't changed.");
180 do_check_eq(johndoe.modified("crypto"), old_key_time);
181
182 _("Resetting HMAC error timer.");
183 Service.lastHMACEvent = 0;
184
185 _("Syncing...");
186 Service.sync();
187 _("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair);
188 _("Server keys have been updated, and we skipped over 5 more HMAC errors without adjusting history.");
189 do_check_true(johndoe.modified("crypto") > old_key_time);
190 do_check_eq(hmacErrorCount, 6);
191 do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--5"));
192 do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--6"));
193 do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--7"));
194 do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--8"));
195 do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--9"));
196 } finally {
197 Svc.Prefs.resetBranch("");
198 let deferred = Promise.defer();
199 server.stop(deferred.resolve);
200 yield deferred.promise;
201 }
202 });
203
204 function run_test() {
205 let logger = Log.repository.rootLogger;
206 Log.repository.rootLogger.addAppender(new Log.DumpAppender());
207
208 ensureLegacyIdentityManager();
209
210 run_next_test();
211 }
212
213 /**
214 * Asynchronously check a url is visited.
215 * @param url the url
216 * @return {Promise}
217 * @resolves When the check has been added successfully.
218 * @rejects JavaScript exception.
219 */
220 function promiseIsURIVisited(url) {
221 let deferred = Promise.defer();
222 PlacesUtils.asyncHistory.isURIVisited(Utils.makeURI(url), function(aURI, aIsVisited) {
223 deferred.resolve(aIsVisited);
224 });
225
226 return deferred.promise;
227 }

mercurial