test_BackupService_enable_disable_encryption.js (10648B)
1 /* Any copyright is dedicated to the Public Domain. 2 https://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const TEST_PASSWORD = "This is some test password."; 7 const TEST_PASSWORD_ALT = "mozilla.org"; 8 9 /** 10 * Creates a backup with this backup service and checks whether it can be 11 * decrypted with the given password. This ensures that encryption is 12 * enabled/disabled as needed, and that the backup service will actually use 13 * the given password if applicable. 14 * 15 * @param {BackupService} bs 16 * The BackupService to use. 17 * @param {string} profilePath 18 * The path to the current profile. 19 * @param {string?} password 20 * The expected password, or null if it should not be encrypted. 21 * @returns {boolean} 22 * True if the password matched, false if something went wrong. 23 */ 24 async function backupServiceUsesPassword(bs, profilePath, password = null) { 25 try { 26 const backup = await bs.createBackup({ profilePath }); 27 let dest = await IOUtils.createUniqueFile( 28 PathUtils.parent(backup.archivePath), 29 "extracted-" + PathUtils.filename(backup.archivePath) 30 ); 31 32 let { isEncrypted } = await bs.sampleArchive(backup.archivePath); 33 let shouldBeEncrypted = password != null; 34 Assert.equal( 35 isEncrypted, 36 shouldBeEncrypted, 37 `Archive is ${shouldBeEncrypted ? "" : "not "}encrypted` 38 ); 39 40 // This should throw if the password is incorrect. 41 await bs.extractCompressedSnapshotFromArchive( 42 backup.archivePath, 43 dest, 44 password 45 ); 46 47 await IOUtils.remove(backup.archivePath); 48 await IOUtils.remove(dest); 49 return true; 50 } catch (e) { 51 console.error(e); 52 return false; 53 } 54 } 55 56 add_setup(async () => { 57 // Much of this setup is copied from toolkit/profile/xpcshell/head.js. It is 58 // needed in order to put the xpcshell test environment into the state where 59 // it thinks its profile is the one pointed at by 60 // nsIToolkitProfileService.currentProfile. 61 let gProfD = do_get_profile(); 62 let gDataHome = gProfD.clone(); 63 gDataHome.append("data"); 64 gDataHome.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755); 65 let gDataHomeLocal = gProfD.clone(); 66 gDataHomeLocal.append("local"); 67 gDataHomeLocal.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755); 68 69 let xreDirProvider = Cc["@mozilla.org/xre/directory-provider;1"].getService( 70 Ci.nsIXREDirProvider 71 ); 72 xreDirProvider.setUserDataDirectory(gDataHome, false); 73 xreDirProvider.setUserDataDirectory(gDataHomeLocal, true); 74 75 let profileSvc = Cc["@mozilla.org/toolkit/profile-service;1"].getService( 76 Ci.nsIToolkitProfileService 77 ); 78 79 let createdProfile = {}; 80 let didCreate = profileSvc.selectStartupProfile( 81 ["xpcshell"], 82 false, 83 AppConstants.UPDATE_CHANNEL, 84 "", 85 {}, 86 {}, 87 createdProfile 88 ); 89 Assert.ok(didCreate, "Created a testing profile and set it to current."); 90 Assert.equal( 91 profileSvc.currentProfile, 92 createdProfile.value, 93 "Profile set to current" 94 ); 95 }); 96 97 /** 98 * Tests that if encryption is disabled that only BackupResource's with 99 * requiresEncryption set to `false` will have `backup()` called on them. 100 */ 101 add_task(async function test_disabled_encryption() { 102 let sandbox = sinon.createSandbox(); 103 104 let bs = new BackupService({ 105 FakeBackupResource1, 106 FakeBackupResource2, 107 FakeBackupResource3, 108 }); 109 110 Assert.ok( 111 !bs.state.encryptionEnabled, 112 "State should indicate that encryption is disabled." 113 ); 114 115 let testProfilePath = await IOUtils.createUniqueDirectory( 116 PathUtils.tempDir, 117 "testDisabledEncryption" 118 ); 119 let encState = await bs.loadEncryptionState(testProfilePath); 120 Assert.ok(!encState, "Should not find an ArchiveEncryptionState."); 121 122 // Override FakeBackupResource2 so that it requires encryption. 123 sandbox.stub(FakeBackupResource2, "requiresEncryption").get(() => { 124 return true; 125 }); 126 Assert.ok( 127 FakeBackupResource2.requiresEncryption, 128 "FakeBackupResource2 requires encryption." 129 ); 130 131 // This is how these FakeBackupResource's are defined in head.js 132 Assert.ok( 133 !FakeBackupResource1.requiresEncryption, 134 "FakeBackupResource1 does not require encryption." 135 ); 136 Assert.ok( 137 !FakeBackupResource3.requiresEncryption, 138 "FakeBackupResource3 does not require encryption." 139 ); 140 141 let resourceWithoutEncryptionStubs = [ 142 sandbox.stub(FakeBackupResource1.prototype, "backup").resolves(null), 143 sandbox.stub(FakeBackupResource3.prototype, "backup").resolves(null), 144 ]; 145 146 let resourceWithEncryptionStub = sandbox 147 .stub(FakeBackupResource2.prototype, "backup") 148 .resolves(null); 149 150 await bs.createBackup({ profilePath: testProfilePath }); 151 Assert.ok( 152 resourceWithEncryptionStub.notCalled, 153 "FakeBackupResource2.backup should not have been called" 154 ); 155 156 for (let resourceWithoutEncryptionStub of resourceWithoutEncryptionStubs) { 157 Assert.ok( 158 resourceWithoutEncryptionStub.calledOnce, 159 "backup called on resource that didn't require encryption" 160 ); 161 } 162 163 await IOUtils.remove(testProfilePath, { recursive: true }); 164 sandbox.restore(); 165 }); 166 167 /** 168 * Tests that if encryption is enabled from a non-enabled state, that an 169 * ArchiveEncryptionState is created, and state is written to the profile 170 * directory. Also tests that this allows BackupResource's with 171 * requiresEncryption set to `true` to have `backup()` called on them. 172 */ 173 add_task(async function test_enable_encryption() { 174 let sandbox = sinon.createSandbox(); 175 176 let bs = new BackupService({ 177 FakeBackupResource1, 178 FakeBackupResource2, 179 FakeBackupResource3, 180 }); 181 182 Assert.ok( 183 !bs.state.encryptionEnabled, 184 "State should initially indicate that encryption is disabled." 185 ); 186 187 let testProfilePath = await IOUtils.createUniqueDirectory( 188 PathUtils.tempDir, 189 "testEnableEncryption" 190 ); 191 let encState = await bs.loadEncryptionState(testProfilePath); 192 Assert.ok(!encState, "Should not find an ArchiveEncryptionState."); 193 194 // Now enable encryption. 195 let stateUpdatePromise = new Promise(resolve => { 196 bs.addEventListener("BackupService:StateUpdate", resolve, { once: true }); 197 }); 198 await bs.enableEncryption(TEST_PASSWORD, testProfilePath); 199 await stateUpdatePromise; 200 Assert.ok( 201 bs.state.encryptionEnabled, 202 "State should indicate that encryption is enabled." 203 ); 204 205 Assert.ok( 206 await IOUtils.exists( 207 PathUtils.join( 208 testProfilePath, 209 BackupService.PROFILE_FOLDER_NAME, 210 BackupService.ARCHIVE_ENCRYPTION_STATE_FILE 211 ) 212 ), 213 "Encryption state file should exist." 214 ); 215 216 // Override FakeBackupResource2 so that it requires encryption. 217 sandbox.stub(FakeBackupResource2, "requiresEncryption").get(() => { 218 return true; 219 }); 220 Assert.ok( 221 FakeBackupResource2.requiresEncryption, 222 "FakeBackupResource2 requires encryption." 223 ); 224 225 // This is how these FakeBackupResource's are defined in head.js 226 Assert.ok( 227 !FakeBackupResource1.requiresEncryption, 228 "FakeBackupResource1 does not require encryption." 229 ); 230 Assert.ok( 231 !FakeBackupResource3.requiresEncryption, 232 "FakeBackupResource3 does not require encryption." 233 ); 234 235 let allResourceBackupStubs = [ 236 sandbox.stub(FakeBackupResource1.prototype, "backup").resolves(null), 237 sandbox.stub(FakeBackupResource3.prototype, "backup").resolves(null), 238 sandbox.stub(FakeBackupResource2.prototype, "backup").resolves(null), 239 ]; 240 241 await bs.createBackup({ 242 profilePath: testProfilePath, 243 }); 244 245 for (let resourceBackupStub of allResourceBackupStubs) { 246 Assert.ok(resourceBackupStub.calledOnce, "backup called on resource"); 247 } 248 249 Assert.ok( 250 await IOUtils.exists( 251 PathUtils.join( 252 testProfilePath, 253 BackupService.PROFILE_FOLDER_NAME, 254 BackupService.ARCHIVE_ENCRYPTION_STATE_FILE 255 ) 256 ), 257 "Encryption state file should still exist." 258 ); 259 260 await IOUtils.remove(testProfilePath, { recursive: true }); 261 sandbox.restore(); 262 }); 263 264 /** 265 * Tests that enabling encryption while it is already enabled changes the 266 * password. 267 */ 268 add_task(async function test_change_encryption_password() { 269 let bs = new BackupService(); 270 271 let testProfilePath = await IOUtils.createUniqueDirectory( 272 PathUtils.tempDir, 273 "testAlreadyEnabledEncryption" 274 ); 275 276 // Enable encryption. 277 await bs.enableEncryption(TEST_PASSWORD, testProfilePath); 278 279 let encState = await bs.loadEncryptionState(testProfilePath); 280 Assert.ok(encState, "ArchiveEncryptionState is available."); 281 Assert.ok( 282 await backupServiceUsesPassword(bs, testProfilePath, TEST_PASSWORD), 283 "BackupService is using TEST_PASSWORD" 284 ); 285 286 // Change the password. 287 await bs.enableEncryption(TEST_PASSWORD_ALT, testProfilePath); 288 encState = await bs.loadEncryptionState(testProfilePath); 289 Assert.ok(encState, "ArchiveEncryptionState is still available."); 290 Assert.ok( 291 await backupServiceUsesPassword(bs, testProfilePath, TEST_PASSWORD_ALT), 292 "BackupService is using TEST_PASSWORD_ALT" 293 ); 294 295 await IOUtils.remove(testProfilePath, { recursive: true }); 296 }); 297 298 /** 299 * Tests that if encryption is enabled that it can be disabled. 300 */ 301 add_task(async function test_disabling_encryption() { 302 let bs = new BackupService(); 303 304 let testProfilePath = await IOUtils.createUniqueDirectory( 305 PathUtils.tempDir, 306 "testDisableEncryption" 307 ); 308 309 // Enable encryption. 310 await bs.enableEncryption(TEST_PASSWORD, testProfilePath); 311 312 let encState = await bs.loadEncryptionState(testProfilePath); 313 Assert.ok(encState, "ArchiveEncryptionState is available."); 314 Assert.ok( 315 bs.state.encryptionEnabled, 316 "State should indicate that encryption is enabled." 317 ); 318 319 Assert.ok( 320 await IOUtils.exists( 321 PathUtils.join( 322 testProfilePath, 323 BackupService.PROFILE_FOLDER_NAME, 324 BackupService.ARCHIVE_ENCRYPTION_STATE_FILE 325 ) 326 ), 327 "Encryption state file should exist." 328 ); 329 330 // Now disable encryption. 331 let stateUpdatePromise = new Promise(resolve => { 332 bs.addEventListener("BackupService:StateUpdate", resolve, { once: true }); 333 }); 334 await bs.disableEncryption(testProfilePath); 335 await stateUpdatePromise; 336 Assert.ok( 337 !bs.state.encryptionEnabled, 338 "State should indicate that encryption is now disabled." 339 ); 340 341 Assert.ok( 342 !(await IOUtils.exists( 343 PathUtils.join( 344 testProfilePath, 345 BackupService.PROFILE_FOLDER_NAME, 346 BackupService.ARCHIVE_ENCRYPTION_STATE_FILE 347 ) 348 )), 349 "Encryption state file should have been removed." 350 ); 351 Assert.ok( 352 await backupServiceUsesPassword(bs, testProfilePath, null), 353 "BackupService should not use encryption." 354 ); 355 356 await IOUtils.remove(testProfilePath, { recursive: true }); 357 });