test_service_detect_upgrade.js (9087B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 const { CryptoWrapper, WBORecord } = ChromeUtils.importESModule( 5 "resource://services-sync/record.sys.mjs" 6 ); 7 const { Service } = ChromeUtils.importESModule( 8 "resource://services-sync/service.sys.mjs" 9 ); 10 11 add_task(async function v4_upgrade() { 12 enableValidationPrefs(); 13 14 let clients = new ServerCollection(); 15 let meta_global = new ServerWBO("global"); 16 17 // Tracking info/collections. 18 let collectionsHelper = track_collections_helper(); 19 let upd = collectionsHelper.with_updated_collection; 20 let collections = collectionsHelper.collections; 21 22 let keysWBO = new ServerWBO("keys"); 23 let server = httpd_setup({ 24 // Special. 25 "/1.1/johndoe/info/collections": collectionsHelper.handler, 26 "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), 27 "/1.1/johndoe/storage/meta/global": upd("meta", meta_global.handler()), 28 29 // Track modified times. 30 "/1.1/johndoe/storage/clients": upd("clients", clients.handler()), 31 "/1.1/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()), 32 33 // Just so we don't get 404s in the logs. 34 "/1.1/johndoe/storage/bookmarks": new ServerCollection().handler(), 35 "/1.1/johndoe/storage/forms": new ServerCollection().handler(), 36 "/1.1/johndoe/storage/history": new ServerCollection().handler(), 37 "/1.1/johndoe/storage/passwords": new ServerCollection().handler(), 38 "/1.1/johndoe/storage/prefs": new ServerCollection().handler(), 39 }); 40 41 try { 42 Service.status.resetSync(); 43 44 _("Logging in."); 45 46 await configureIdentity({ username: "johndoe" }, server); 47 48 await Service.login(); 49 Assert.ok(Service.isLoggedIn); 50 await Service.verifyAndFetchSymmetricKeys(); 51 Assert.ok(await Service._remoteSetup()); 52 53 async function test_out_of_date() { 54 _("Old meta/global: " + JSON.stringify(meta_global)); 55 meta_global.payload = JSON.stringify({ 56 syncID: "foooooooooooooooooooooooooo", 57 storageVersion: STORAGE_VERSION + 1, 58 }); 59 collections.meta = Date.now() / 1000; 60 _("New meta/global: " + JSON.stringify(meta_global)); 61 Service.recordManager.set(Service.metaURL, meta_global); 62 try { 63 await Service.sync(); 64 } catch (ex) {} 65 Assert.equal(Service.status.sync, VERSION_OUT_OF_DATE); 66 } 67 68 // See what happens when we bump the storage version. 69 _("Syncing after server has been upgraded."); 70 await test_out_of_date(); 71 72 // Same should happen after a wipe. 73 _("Syncing after server has been upgraded and wiped."); 74 await Service.wipeServer(); 75 await test_out_of_date(); 76 77 // Now's a great time to test what happens when keys get replaced. 78 _("Syncing afresh..."); 79 Service.logout(); 80 Service.collectionKeys.clear(); 81 meta_global.payload = JSON.stringify({ 82 syncID: "foooooooooooooobbbbbbbbbbbb", 83 storageVersion: STORAGE_VERSION, 84 }); 85 collections.meta = Date.now() / 1000; 86 Service.recordManager.set(Service.metaURL, meta_global); 87 await Service.login(); 88 Assert.ok(Service.isLoggedIn); 89 await Service.sync(); 90 Assert.ok(Service.isLoggedIn); 91 92 let serverDecrypted; 93 let serverKeys; 94 let serverResp; 95 96 async function retrieve_server_default() { 97 serverKeys = serverResp = serverDecrypted = null; 98 99 serverKeys = new CryptoWrapper("crypto", "keys"); 100 serverResp = ( 101 await serverKeys.fetch(Service.resource(Service.cryptoKeysURL)) 102 ).response; 103 Assert.ok(serverResp.success); 104 105 serverDecrypted = await serverKeys.decrypt( 106 Service.identity.syncKeyBundle 107 ); 108 _("Retrieved WBO: " + JSON.stringify(serverDecrypted)); 109 _("serverKeys: " + JSON.stringify(serverKeys)); 110 111 return serverDecrypted.default; 112 } 113 114 async function retrieve_and_compare_default(should_succeed) { 115 let serverDefault = await retrieve_server_default(); 116 let localDefault = Service.collectionKeys.keyForCollection().keyPairB64; 117 118 _("Retrieved keyBundle: " + JSON.stringify(serverDefault)); 119 _("Local keyBundle: " + JSON.stringify(localDefault)); 120 121 if (should_succeed) { 122 Assert.equal( 123 JSON.stringify(serverDefault), 124 JSON.stringify(localDefault) 125 ); 126 } else { 127 Assert.notEqual( 128 JSON.stringify(serverDefault), 129 JSON.stringify(localDefault) 130 ); 131 } 132 } 133 134 // Uses the objects set above. 135 async function set_server_keys(pair) { 136 serverDecrypted.default = pair; 137 serverKeys.cleartext = serverDecrypted; 138 await serverKeys.encrypt(Service.identity.syncKeyBundle); 139 await serverKeys.upload(Service.resource(Service.cryptoKeysURL)); 140 } 141 142 _("Checking we have the latest keys."); 143 await retrieve_and_compare_default(true); 144 145 _("Update keys on server."); 146 await set_server_keys([ 147 "KaaaaaaaaaaaHAtfmuRY0XEJ7LXfFuqvF7opFdBD/MY=", 148 "aaaaaaaaaaaapxMO6TEWtLIOv9dj6kBAJdzhWDkkkis=", 149 ]); 150 151 _("Checking that we no longer have the latest keys."); 152 await retrieve_and_compare_default(false); 153 154 _("Indeed, they're what we set them to..."); 155 Assert.equal( 156 "KaaaaaaaaaaaHAtfmuRY0XEJ7LXfFuqvF7opFdBD/MY=", 157 (await retrieve_server_default())[0] 158 ); 159 160 _("Sync. Should download changed keys automatically."); 161 let oldClientsModified = collections.clients; 162 let oldTabsModified = collections.tabs; 163 164 await Service.login(); 165 await Service.sync(); 166 _("New key should have forced upload of data."); 167 _("Tabs: " + oldTabsModified + " < " + collections.tabs); 168 _("Clients: " + oldClientsModified + " < " + collections.clients); 169 Assert.greater(collections.clients, oldClientsModified); 170 Assert.greater(collections.tabs, oldTabsModified); 171 172 _("... and keys will now match."); 173 await retrieve_and_compare_default(true); 174 175 // Clean up. 176 await Service.startOver(); 177 } finally { 178 for (const pref of Svc.PrefBranch.getChildList("")) { 179 Svc.PrefBranch.clearUserPref(pref); 180 } 181 await promiseStopServer(server); 182 } 183 }); 184 185 add_task(async function v5_upgrade() { 186 enableValidationPrefs(); 187 188 // Tracking info/collections. 189 let collectionsHelper = track_collections_helper(); 190 let upd = collectionsHelper.with_updated_collection; 191 192 let keysWBO = new ServerWBO("keys"); 193 let bulkWBO = new ServerWBO("bulk"); 194 let clients = new ServerCollection(); 195 let meta_global = new ServerWBO("global"); 196 197 let server = httpd_setup({ 198 // Special. 199 "/1.1/johndoe/storage/meta/global": upd("meta", meta_global.handler()), 200 "/1.1/johndoe/info/collections": collectionsHelper.handler, 201 "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), 202 "/1.1/johndoe/storage/crypto/bulk": upd("crypto", bulkWBO.handler()), 203 204 // Track modified times. 205 "/1.1/johndoe/storage/clients": upd("clients", clients.handler()), 206 "/1.1/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()), 207 }); 208 209 try { 210 Service.status.resetSync(); 211 212 Service.clusterURL = server.baseURI + "/"; 213 214 await configureIdentity({ username: "johndoe" }, server); 215 216 // Test an upgrade where the contents of the server would cause us to error 217 // -- keys decrypted with a different sync key, for example. 218 _("Testing v4 -> v5 (or similar) upgrade."); 219 async function update_server_keys(syncKeyBundle, wboName, collWBO) { 220 await generateNewKeys(Service.collectionKeys); 221 let serverKeys = Service.collectionKeys.asWBO("crypto", wboName); 222 await serverKeys.encrypt(syncKeyBundle); 223 let res = Service.resource(Service.storageURL + collWBO); 224 Assert.ok((await serverKeys.upload(res)).success); 225 } 226 227 _("Bumping version."); 228 // Bump version on the server. 229 let m = new WBORecord("meta", "global"); 230 m.payload = { 231 syncID: "foooooooooooooooooooooooooo", 232 storageVersion: STORAGE_VERSION + 1, 233 }; 234 await m.upload(Service.resource(Service.metaURL)); 235 236 _("New meta/global: " + JSON.stringify(meta_global)); 237 238 // Fill the keys with bad data. 239 let badKeys = new BulkKeyBundle("crypto"); 240 await badKeys.generateRandom(); 241 await update_server_keys(badKeys, "keys", "crypto/keys"); // v4 242 await update_server_keys(badKeys, "bulk", "crypto/bulk"); // v5 243 244 _("Generating new keys."); 245 await generateNewKeys(Service.collectionKeys); 246 247 // Now sync and see what happens. It should be a version fail, not a crypto 248 // fail. 249 250 _("Logging in."); 251 try { 252 await Service.login(); 253 } catch (e) { 254 _("Exception: " + e); 255 } 256 _("Status: " + Service.status); 257 Assert.ok(!Service.isLoggedIn); 258 Assert.equal(VERSION_OUT_OF_DATE, Service.status.sync); 259 260 // Clean up. 261 await Service.startOver(); 262 } finally { 263 for (const pref of Svc.PrefBranch.getChildList("")) { 264 Svc.PrefBranch.clearUserPref(pref); 265 } 266 await promiseStopServer(server); 267 } 268 }); 269 270 function run_test() { 271 Log.repository.rootLogger.addAppender(new Log.DumpAppender()); 272 273 run_next_test(); 274 }