diff options
Diffstat (limited to 'services/sync/tests/unit/test_corrupt_keys.js')
-rw-r--r-- | services/sync/tests/unit/test_corrupt_keys.js | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/services/sync/tests/unit/test_corrupt_keys.js b/services/sync/tests/unit/test_corrupt_keys.js new file mode 100644 index 0000000000..b62c65011f --- /dev/null +++ b/services/sync/tests/unit/test_corrupt_keys.js @@ -0,0 +1,246 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Weave } = ChromeUtils.importESModule( + "resource://services-sync/main.sys.mjs" +); +const { HistoryEngine } = ChromeUtils.importESModule( + "resource://services-sync/engines/history.sys.mjs" +); +const { CryptoWrapper, WBORecord } = ChromeUtils.importESModule( + "resource://services-sync/record.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +add_task(async function test_locally_changed_keys() { + enableValidationPrefs(); + + let hmacErrorCount = 0; + function counting(f) { + return async function () { + hmacErrorCount++; + return f.call(this); + }; + } + + Service.handleHMACEvent = counting(Service.handleHMACEvent); + + let server = new SyncServer(); + let johndoe = server.registerUser("johndoe", "password"); + johndoe.createContents({ + meta: {}, + crypto: {}, + clients: {}, + }); + server.start(); + + try { + Svc.Prefs.set("registerEngines", "Tab"); + + await configureIdentity({ username: "johndoe" }, server); + // We aren't doing a .login yet, so fudge the cluster URL. + Service.clusterURL = Service.identity._token.endpoint; + + await Service.engineManager.register(HistoryEngine); + // Disable addon sync because AddonManager won't be initialized here. + await Service.engineManager.unregister("addons"); + await Service.engineManager.unregister("extension-storage"); + + async function corrupt_local_keys() { + Service.collectionKeys._default.keyPair = [ + await Weave.Crypto.generateRandomKey(), + await Weave.Crypto.generateRandomKey(), + ]; + } + + _("Setting meta."); + + // Bump version on the server. + let m = new WBORecord("meta", "global"); + m.payload = { + syncID: "foooooooooooooooooooooooooo", + storageVersion: STORAGE_VERSION, + }; + await m.upload(Service.resource(Service.metaURL)); + + _( + "New meta/global: " + + JSON.stringify(johndoe.collection("meta").wbo("global")) + ); + + // Upload keys. + await generateNewKeys(Service.collectionKeys); + let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); + await serverKeys.encrypt(Service.identity.syncKeyBundle); + Assert.ok( + (await serverKeys.upload(Service.resource(Service.cryptoKeysURL))).success + ); + + // Check that login works. + Assert.ok(await Service.login()); + Assert.ok(Service.isLoggedIn); + + // Sync should upload records. + await sync_and_validate_telem(); + + // Tabs exist. + _("Tabs modified: " + johndoe.modified("tabs")); + Assert.ok(johndoe.modified("tabs") > 0); + + // Let's create some server side history records. + let liveKeys = Service.collectionKeys.keyForCollection("history"); + _("Keys now: " + liveKeys.keyPair); + let visitType = Ci.nsINavHistoryService.TRANSITION_LINK; + let history = johndoe.createCollection("history"); + for (let i = 0; i < 5; i++) { + let id = "record-no--" + i; + let modified = Date.now() / 1000 - 60 * (i + 10); + + let w = new CryptoWrapper("history", "id"); + w.cleartext = { + id, + histUri: "http://foo/bar?" + id, + title: id, + sortindex: i, + visits: [{ date: (modified - 5) * 1000000, type: visitType }], + deleted: false, + }; + await w.encrypt(liveKeys); + + let payload = { ciphertext: w.ciphertext, IV: w.IV, hmac: w.hmac }; + history.insert(id, payload, modified); + } + + history.timestamp = Date.now() / 1000; + let old_key_time = johndoe.modified("crypto"); + _("Old key time: " + old_key_time); + + // Check that we can decrypt one. + let rec = new CryptoWrapper("history", "record-no--0"); + await rec.fetch( + Service.resource(Service.storageURL + "history/record-no--0") + ); + _(JSON.stringify(rec)); + Assert.ok(!!(await rec.decrypt(liveKeys))); + + Assert.equal(hmacErrorCount, 0); + + // Fill local key cache with bad data. + await corrupt_local_keys(); + _( + "Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair + ); + + Assert.equal(hmacErrorCount, 0); + + _("HMAC error count: " + hmacErrorCount); + // Now syncing should succeed, after one HMAC error. + await sync_and_validate_telem(ping => { + Assert.equal( + ping.engines.find(e => e.name == "history").incoming.applied, + 5 + ); + }); + + Assert.equal(hmacErrorCount, 1); + _( + "Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair + ); + + // And look! We downloaded history! + Assert.ok( + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--0") + ); + Assert.ok( + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--1") + ); + Assert.ok( + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--2") + ); + Assert.ok( + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--3") + ); + Assert.ok( + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--4") + ); + Assert.equal(hmacErrorCount, 1); + + _("Busting some new server values."); + // Now what happens if we corrupt the HMAC on the server? + for (let i = 5; i < 10; i++) { + let id = "record-no--" + i; + let modified = 1 + Date.now() / 1000; + + let w = new CryptoWrapper("history", "id"); + w.cleartext = { + id, + histUri: "http://foo/bar?" + id, + title: id, + sortindex: i, + visits: [{ date: (modified - 5) * 1000000, type: visitType }], + deleted: false, + }; + await w.encrypt(Service.collectionKeys.keyForCollection("history")); + w.hmac = w.hmac.toUpperCase(); + + let payload = { ciphertext: w.ciphertext, IV: w.IV, hmac: w.hmac }; + history.insert(id, payload, modified); + } + history.timestamp = Date.now() / 1000; + + _("Server key time hasn't changed."); + Assert.equal(johndoe.modified("crypto"), old_key_time); + + _("Resetting HMAC error timer."); + Service.lastHMACEvent = 0; + + _("Syncing..."); + await sync_and_validate_telem(ping => { + Assert.equal( + ping.engines.find(e => e.name == "history").incoming.failed, + 5 + ); + }); + + _( + "Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair + ); + _( + "Server keys have been updated, and we skipped over 5 more HMAC errors without adjusting history." + ); + Assert.ok(johndoe.modified("crypto") > old_key_time); + Assert.equal(hmacErrorCount, 6); + Assert.equal( + false, + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--5") + ); + Assert.equal( + false, + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--6") + ); + Assert.equal( + false, + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--7") + ); + Assert.equal( + false, + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--8") + ); + Assert.equal( + false, + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--9") + ); + } finally { + Svc.Prefs.resetBranch(""); + await promiseStopServer(server); + } +}); + +function run_test() { + Log.repository.rootLogger.addAppender(new Log.DumpAppender()); + validate_all_future_pings(); + + run_next_test(); +} |