/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ 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 v4_upgrade() { enableValidationPrefs(); let clients = new ServerCollection(); let meta_global = new ServerWBO("global"); // Tracking info/collections. let collectionsHelper = track_collections_helper(); let upd = collectionsHelper.with_updated_collection; let collections = collectionsHelper.collections; let keysWBO = new ServerWBO("keys"); let server = httpd_setup({ // Special. "/1.1/johndoe/info/collections": collectionsHelper.handler, "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), "/1.1/johndoe/storage/meta/global": upd("meta", meta_global.handler()), // Track modified times. "/1.1/johndoe/storage/clients": upd("clients", clients.handler()), "/1.1/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()), // Just so we don't get 404s in the logs. "/1.1/johndoe/storage/bookmarks": new ServerCollection().handler(), "/1.1/johndoe/storage/forms": new ServerCollection().handler(), "/1.1/johndoe/storage/history": new ServerCollection().handler(), "/1.1/johndoe/storage/passwords": new ServerCollection().handler(), "/1.1/johndoe/storage/prefs": new ServerCollection().handler(), }); try { Service.status.resetSync(); _("Logging in."); await configureIdentity({ username: "johndoe" }, server); await Service.login(); Assert.ok(Service.isLoggedIn); await Service.verifyAndFetchSymmetricKeys(); Assert.ok(await Service._remoteSetup()); async function test_out_of_date() { _("Old meta/global: " + JSON.stringify(meta_global)); meta_global.payload = JSON.stringify({ syncID: "foooooooooooooooooooooooooo", storageVersion: STORAGE_VERSION + 1, }); collections.meta = Date.now() / 1000; _("New meta/global: " + JSON.stringify(meta_global)); Service.recordManager.set(Service.metaURL, meta_global); try { await Service.sync(); } catch (ex) {} Assert.equal(Service.status.sync, VERSION_OUT_OF_DATE); } // See what happens when we bump the storage version. _("Syncing after server has been upgraded."); await test_out_of_date(); // Same should happen after a wipe. _("Syncing after server has been upgraded and wiped."); await Service.wipeServer(); await test_out_of_date(); // Now's a great time to test what happens when keys get replaced. _("Syncing afresh..."); Service.logout(); Service.collectionKeys.clear(); meta_global.payload = JSON.stringify({ syncID: "foooooooooooooobbbbbbbbbbbb", storageVersion: STORAGE_VERSION, }); collections.meta = Date.now() / 1000; Service.recordManager.set(Service.metaURL, meta_global); await Service.login(); Assert.ok(Service.isLoggedIn); await Service.sync(); Assert.ok(Service.isLoggedIn); let serverDecrypted; let serverKeys; let serverResp; async function retrieve_server_default() { serverKeys = serverResp = serverDecrypted = null; serverKeys = new CryptoWrapper("crypto", "keys"); serverResp = ( await serverKeys.fetch(Service.resource(Service.cryptoKeysURL)) ).response; Assert.ok(serverResp.success); serverDecrypted = await serverKeys.decrypt( Service.identity.syncKeyBundle ); _("Retrieved WBO: " + JSON.stringify(serverDecrypted)); _("serverKeys: " + JSON.stringify(serverKeys)); return serverDecrypted.default; } async function retrieve_and_compare_default(should_succeed) { let serverDefault = await retrieve_server_default(); let localDefault = Service.collectionKeys.keyForCollection().keyPairB64; _("Retrieved keyBundle: " + JSON.stringify(serverDefault)); _("Local keyBundle: " + JSON.stringify(localDefault)); if (should_succeed) { Assert.equal( JSON.stringify(serverDefault), JSON.stringify(localDefault) ); } else { Assert.notEqual( JSON.stringify(serverDefault), JSON.stringify(localDefault) ); } } // Uses the objects set above. async function set_server_keys(pair) { serverDecrypted.default = pair; serverKeys.cleartext = serverDecrypted; await serverKeys.encrypt(Service.identity.syncKeyBundle); await serverKeys.upload(Service.resource(Service.cryptoKeysURL)); } _("Checking we have the latest keys."); await retrieve_and_compare_default(true); _("Update keys on server."); await set_server_keys([ "KaaaaaaaaaaaHAtfmuRY0XEJ7LXfFuqvF7opFdBD/MY=", "aaaaaaaaaaaapxMO6TEWtLIOv9dj6kBAJdzhWDkkkis=", ]); _("Checking that we no longer have the latest keys."); await retrieve_and_compare_default(false); _("Indeed, they're what we set them to..."); Assert.equal( "KaaaaaaaaaaaHAtfmuRY0XEJ7LXfFuqvF7opFdBD/MY=", (await retrieve_server_default())[0] ); _("Sync. Should download changed keys automatically."); let oldClientsModified = collections.clients; let oldTabsModified = collections.tabs; await Service.login(); await Service.sync(); _("New key should have forced upload of data."); _("Tabs: " + oldTabsModified + " < " + collections.tabs); _("Clients: " + oldClientsModified + " < " + collections.clients); Assert.ok(collections.clients > oldClientsModified); Assert.ok(collections.tabs > oldTabsModified); _("... and keys will now match."); await retrieve_and_compare_default(true); // Clean up. await Service.startOver(); } finally { for (const pref of Svc.PrefBranch.getChildList("")) { Svc.PrefBranch.clearUserPref(pref); } await promiseStopServer(server); } }); add_task(async function v5_upgrade() { enableValidationPrefs(); // Tracking info/collections. let collectionsHelper = track_collections_helper(); let upd = collectionsHelper.with_updated_collection; let keysWBO = new ServerWBO("keys"); let bulkWBO = new ServerWBO("bulk"); let clients = new ServerCollection(); let meta_global = new ServerWBO("global"); let server = httpd_setup({ // Special. "/1.1/johndoe/storage/meta/global": upd("meta", meta_global.handler()), "/1.1/johndoe/info/collections": collectionsHelper.handler, "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), "/1.1/johndoe/storage/crypto/bulk": upd("crypto", bulkWBO.handler()), // Track modified times. "/1.1/johndoe/storage/clients": upd("clients", clients.handler()), "/1.1/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()), }); try { Service.status.resetSync(); Service.clusterURL = server.baseURI + "/"; await configureIdentity({ username: "johndoe" }, server); // Test an upgrade where the contents of the server would cause us to error // -- keys decrypted with a different sync key, for example. _("Testing v4 -> v5 (or similar) upgrade."); async function update_server_keys(syncKeyBundle, wboName, collWBO) { await generateNewKeys(Service.collectionKeys); let serverKeys = Service.collectionKeys.asWBO("crypto", wboName); await serverKeys.encrypt(syncKeyBundle); let res = Service.resource(Service.storageURL + collWBO); Assert.ok((await serverKeys.upload(res)).success); } _("Bumping version."); // Bump version on the server. let m = new WBORecord("meta", "global"); m.payload = { syncID: "foooooooooooooooooooooooooo", storageVersion: STORAGE_VERSION + 1, }; await m.upload(Service.resource(Service.metaURL)); _("New meta/global: " + JSON.stringify(meta_global)); // Fill the keys with bad data. let badKeys = new BulkKeyBundle("crypto"); await badKeys.generateRandom(); await update_server_keys(badKeys, "keys", "crypto/keys"); // v4 await update_server_keys(badKeys, "bulk", "crypto/bulk"); // v5 _("Generating new keys."); await generateNewKeys(Service.collectionKeys); // Now sync and see what happens. It should be a version fail, not a crypto // fail. _("Logging in."); try { await Service.login(); } catch (e) { _("Exception: " + e); } _("Status: " + Service.status); Assert.ok(!Service.isLoggedIn); Assert.equal(VERSION_OUT_OF_DATE, Service.status.sync); // Clean up. await Service.startOver(); } finally { for (const pref of Svc.PrefBranch.getChildList("")) { Svc.PrefBranch.clearUserPref(pref); } await promiseStopServer(server); } }); function run_test() { Log.repository.rootLogger.addAppender(new Log.DumpAppender()); run_next_test(); }