tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 8e18c77bc4ae2d1c02e2e3198acbd01c5072084b
parent f8cb5f5687a1fe95b88bd8e2840737c3c483ddf4
Author: Duncan McIntosh <dmcintosh@mozilla.com>
Date:   Mon, 22 Dec 2025 15:21:42 +0000

Bug 1996249 - Use enableEncryption itself to change the backup password instead of having to disable first. r=kpatenio

Differential Revision: https://phabricator.services.mozilla.com/D272078

Diffstat:
Mbrowser/components/DesktopActorRegistry.sys.mjs | 1-
Mbrowser/components/backup/BackupService.sys.mjs | 28++++------------------------
Mbrowser/components/backup/actors/BackupUIChild.sys.mjs | 9---------
Mbrowser/components/backup/actors/BackupUIParent.sys.mjs | 20++++++--------------
Mbrowser/components/backup/content/enable-backup-encryption.mjs | 40++++++++++------------------------------
Mbrowser/components/backup/content/enable-backup-encryption.stories.mjs | 18+-----------------
Mbrowser/components/backup/tests/browser/browser_settings_enable_backup_encryption.js | 15+++++++--------
Mbrowser/components/backup/tests/chrome/test_enable_backup_encryption.html | 2+-
Mbrowser/components/backup/tests/xpcshell/test_BackupService_enable_disable_encryption.js | 93++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
9 files changed, 95 insertions(+), 131 deletions(-)

diff --git a/browser/components/DesktopActorRegistry.sys.mjs b/browser/components/DesktopActorRegistry.sys.mjs @@ -249,7 +249,6 @@ let JSWINDOWACTORS = { "BackupUI:RestoreFromBackupChooseFile": { wantUntrusted: true }, "BackupUI:EnableEncryption": { wantUntrusted: true }, "BackupUI:DisableEncryption": { wantUntrusted: true }, - "BackupUI:RerunEncryption": { wantUntrusted: true }, "BackupUI:ShowBackupLocation": { wantUntrusted: true }, "BackupUI:EditBackupLocation": { wantUntrusted: true }, "BackupUI:SetEmbeddedComponentPersistentData": { wantUntrusted: true }, diff --git a/browser/components/backup/BackupService.sys.mjs b/browser/components/backup/BackupService.sys.mjs @@ -3914,8 +3914,7 @@ export class BackupService extends EventTarget { /** * Enables encryption for backups, allowing sensitive data to be backed up. - * Throws if encryption is already enabled. After enabling encryption, that - * state is written to disk. + * After enabling encryption, the state is written to disk. * * @throws Exception * @param {string} password @@ -3927,14 +3926,6 @@ export class BackupService extends EventTarget { */ async enableEncryption(password, profilePath = PathUtils.profileDir) { lazy.logConsole.debug("Enabling encryption."); - let encState = await this.loadEncryptionState(profilePath); - if (encState) { - throw new BackupError( - "Encryption is already enabled.", - ERRORS.ENCRYPTION_ALREADY_ENABLED - ); - } - if (!password) { throw new BackupError( "Cannot supply a blank password.", @@ -3949,8 +3940,8 @@ export class BackupService extends EventTarget { ); } - ({ instance: encState } = - await lazy.ArchiveEncryptionState.initialize(password)); + let { instance: encState } = + await lazy.ArchiveEncryptionState.initialize(password); if (!encState) { throw new BackupError( "Failed to construct ArchiveEncryptionState", @@ -3974,7 +3965,7 @@ export class BackupService extends EventTarget { } /** - * Disables encryption of backups. Throws is encryption is already disabled. + * Disables encryption of backups. * * @throws Exception * @param {string} [profilePath=PathUtils.profileDir] @@ -3984,22 +3975,11 @@ export class BackupService extends EventTarget { */ async disableEncryption(profilePath = PathUtils.profileDir) { lazy.logConsole.debug("Disabling encryption."); - let encState = await this.loadEncryptionState(profilePath); - if (!encState) { - throw new BackupError( - "Encryption is already disabled.", - ERRORS.ENCRYPTION_ALREADY_DISABLED - ); - } - let encStateFile = PathUtils.join( profilePath, BackupService.PROFILE_FOLDER_NAME, BackupService.ARCHIVE_ENCRYPTION_STATE_FILE ); - // It'd be pretty strange, but not impossible, for something else to have - // gotten rid of the encryption state file at this point. We'll ignore it - // if that's the case. await IOUtils.remove(encStateFile, { ignoreAbsent: true, retryReadonly: true, diff --git a/browser/components/backup/actors/BackupUIChild.sys.mjs b/browser/components/backup/actors/BackupUIChild.sys.mjs @@ -125,15 +125,6 @@ export class BackupUIChild extends JSWindowActorChild { } else { target.disableEncryptionErrorCode = result.errorCode; } - } else if (event.type == "BackupUI:RerunEncryption") { - const target = event.target; - - const result = await this.sendQuery("RerunEncryption", event.detail); - if (result.success) { - target.close(); - } else { - target.rerunEncryptionErrorCode = result.errorCode; - } } else if (event.type == "BackupUI:ShowBackupLocation") { this.sendAsyncMessage("ShowBackupLocation"); } else if (event.type == "BackupUI:EditBackupLocation") { diff --git a/browser/components/backup/actors/BackupUIParent.sys.mjs b/browser/components/backup/actors/BackupUIParent.sys.mjs @@ -225,8 +225,13 @@ export class BackupUIParent extends JSWindowActorParent { return { success: true }; } else if (message.name == "EnableEncryption") { try { + let wasEncrypted = this.#bs.state.encryptionEnabled; await this.#bs.enableEncryption(message.data.password); - Glean.browserBackup.passwordAdded.record(); + if (wasEncrypted) { + Glean.browserBackup.passwordChanged.record(); + } else { + Glean.browserBackup.passwordAdded.record(); + } } catch (e) { lazy.logConsole.error(`Failed to enable encryption`, e); return { success: false, errorCode: e.cause || lazy.ERRORS.UNKNOWN }; @@ -243,19 +248,6 @@ export class BackupUIParent extends JSWindowActorParent { } return await this.#triggerCreateBackup({ reason: "encryption" }); - } else if (message.name == "RerunEncryption") { - try { - let { password } = message.data; - - await this.#bs.disableEncryption(); - await this.#bs.enableEncryption(password); - Glean.browserBackup.passwordChanged.record(); - } catch (e) { - lazy.logConsole.error(`Failed to rerun encryption`, e); - return { success: false, errorCode: e.cause || lazy.ERRORS.UNKNOWN }; - } - - return await this.#triggerCreateBackup({ reason: "encryption" }); } else if (message.name == "ShowBackupLocation") { this.#bs.showBackupLocation(); } else if (message.name == "EditBackupLocation") { diff --git a/browser/components/backup/content/enable-backup-encryption.mjs b/browser/components/backup/content/enable-backup-encryption.mjs @@ -61,7 +61,6 @@ export default class EnableBackupEncryption extends MozLitElement { // managed by BackupUIChild enableEncryptionErrorCode: { type: Number }, - rerunEncryptionErrorCode: { type: Number }, }; static get queries() { @@ -83,7 +82,6 @@ export default class EnableBackupEncryption extends MozLitElement { this._inputPassValue = ""; this._passwordsMatch = false; this.enableEncryptionErrorCode = 0; - this.rerunEncryptionErrorCode = 0; } connectedCallback() { @@ -121,28 +119,14 @@ export default class EnableBackupEncryption extends MozLitElement { } handleConfirm() { - switch (this.type) { - case VALID_TYPES.SET_PASSWORD: - this.dispatchEvent( - new CustomEvent("BackupUI:EnableEncryption", { - bubbles: true, - detail: { - password: this._inputPassValue, - }, - }) - ); - break; - case VALID_TYPES.CHANGE_PASSWORD: - this.dispatchEvent( - new CustomEvent("BackupUI:RerunEncryption", { - bubbles: true, - detail: { - password: this._inputPassValue, - }, - }) - ); - break; - } + this.dispatchEvent( + new CustomEvent("BackupUI:EnableEncryption", { + bubbles: true, + detail: { + password: this._inputPassValue, + }, + }) + ); } descriptionTemplate() { @@ -184,9 +168,7 @@ export default class EnableBackupEncryption extends MozLitElement { } errorTemplate() { - let messageId = this.enableEncryptionErrorCode - ? getErrorL10nId(this.enableEncryptionErrorCode) - : getErrorL10nId(this.rerunEncryptionErrorCode); + let messageId = getErrorL10nId(this.enableEncryptionErrorCode); return html` <moz-message-bar id="enable-backup-encryption-error" @@ -218,9 +200,7 @@ export default class EnableBackupEncryption extends MozLitElement { > </password-validation-inputs> - ${this.enableEncryptionErrorCode || this.rerunEncryptionErrorCode - ? this.errorTemplate() - : null} + ${this.enableEncryptionErrorCode ? this.errorTemplate() : null} </div> ${this.buttonGroupTemplate()} </div> diff --git a/browser/components/backup/content/enable-backup-encryption.stories.mjs b/browser/components/backup/content/enable-backup-encryption.stories.mjs @@ -29,24 +29,14 @@ export default { mapping: SELECTABLE_ERRORS, control: { type: "select" }, }, - rerunEncryptionErrorCode: { - options: Object.keys(SELECTABLE_ERRORS), - mapping: SELECTABLE_ERRORS, - control: { type: "select" }, - }, }, }; -const Template = ({ - type, - enableEncryptionErrorCode, - rerunEncryptionErrorCode, -}) => html` +const Template = ({ type, enableEncryptionErrorCode }) => html` <moz-card style="width: 23.94rem; position: relative;"> <enable-backup-encryption type=${type} .enableEncryptionErrorCode=${enableEncryptionErrorCode} - .rerunEncryptionErrorCode=${rerunEncryptionErrorCode} ></enable-backup-encryption> </moz-card> `; @@ -66,9 +56,3 @@ SetPasswordError.args = { type: "set-password", enableEncryptionErrorCode: ERRORS.INVALID_PASSWORD, }; - -export const ChangePasswordError = Template.bind({}); -ChangePasswordError.args = { - type: "change-password", - rerunEncryptionErrorCode: ERRORS.INVALID_PASSWORD, -}; diff --git a/browser/components/backup/tests/browser/browser_settings_enable_backup_encryption.js b/browser/components/backup/tests/browser/browser_settings_enable_backup_encryption.js @@ -155,9 +155,6 @@ add_task( let enableEncryptionStub = sandbox .stub(BackupService.prototype, "enableEncryption") .resolves(true); - let disableEncryptionStub = sandbox - .stub(BackupService.prototype, "disableEncryption") - .resolves(true); let createBackupStub = sandbox .stub(BackupService.prototype, "createBackup") .resolves(true); @@ -228,18 +225,20 @@ add_task( await settings.updateComplete; confirmButton = settings.enableBackupEncryptionEl.confirmButtonEl; + let initialState = BackupService.get().state; + sandbox.stub(BackupService.get(), "state").get(() => ({ + ...initialState, + encryptionEnabled: true, + })); + let promise = BrowserTestUtils.waitForEvent( window, - "BackupUI:RerunEncryption" + "BackupUI:EnableEncryption" ); confirmButton.click(); await promise; Assert.ok( - disableEncryptionStub.calledOnce, - "BackupService was called to disable encryption first before registering the changed password" - ); - Assert.ok( enableEncryptionStub.calledOnceWith(MOCK_PASSWORD), "BackupService was called to re-run encryption with changed password" ); diff --git a/browser/components/backup/tests/chrome/test_enable_backup_encryption.html b/browser/components/backup/tests/chrome/test_enable_backup_encryption.html @@ -87,7 +87,7 @@ ok(!confirmButton.disabled, "Confirm button should no longer be disabled"); let content = document.getElementById("content"); - let encryptionPromise = BrowserTestUtils.waitForEvent(content, "BackupUI:RerunEncryption"); + let encryptionPromise = BrowserTestUtils.waitForEvent(content, "BackupUI:EnableEncryption"); confirmButton.click() diff --git a/browser/components/backup/tests/xpcshell/test_BackupService_enable_disable_encryption.js b/browser/components/backup/tests/xpcshell/test_BackupService_enable_disable_encryption.js @@ -4,6 +4,54 @@ https://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const TEST_PASSWORD = "This is some test password."; +const TEST_PASSWORD_ALT = "mozilla.org"; + +/** + * Creates a backup with this backup service and checks whether it can be + * decrypted with the given password. This ensures that encryption is + * enabled/disabled as needed, and that the backup service will actually use + * the given password if applicable. + * + * @param {BackupService} bs + * The BackupService to use. + * @param {string} profilePath + * The path to the current profile. + * @param {string?} password + * The expected password, or null if it should not be encrypted. + * @returns {boolean} + * True if the password matched, false if something went wrong. + */ +async function backupServiceUsesPassword(bs, profilePath, password = null) { + try { + const backup = await bs.createBackup({ profilePath }); + let dest = await IOUtils.createUniqueFile( + PathUtils.parent(backup.archivePath), + "extracted-" + PathUtils.filename(backup.archivePath) + ); + + let { isEncrypted } = await bs.sampleArchive(backup.archivePath); + let shouldBeEncrypted = password != null; + Assert.equal( + isEncrypted, + shouldBeEncrypted, + `Archive is ${shouldBeEncrypted ? "" : "not "}encrypted` + ); + + // This should throw if the password is incorrect. + await bs.extractCompressedSnapshotFromArchive( + backup.archivePath, + dest, + password + ); + + await IOUtils.remove(backup.archivePath); + await IOUtils.remove(dest); + return true; + } catch (e) { + console.error(e); + return false; + } +} add_setup(async () => { // Much of this setup is copied from toolkit/profile/xpcshell/head.js. It is @@ -117,27 +165,6 @@ add_task(async function test_disabled_encryption() { }); /** - * Tests that encryption cannot be disabled if it's already disabled. - */ -add_task(async function test_already_disabled_encryption() { - let bs = new BackupService(); - - Assert.ok( - !bs.state.encryptionEnabled, - "State should indicate that encryption is disabled." - ); - - let encState = await bs.loadEncryptionState(); - Assert.ok(!encState, "Should not find an ArchiveEncryptionState."); - - await Assert.rejects( - bs.disableEncryption(), - /already disabled/, - "It should not be possible to disable encryption if it's already disabled" - ); -}); - -/** * Tests that if encryption is enabled from a non-enabled state, that an * ArchiveEncryptionState is created, and state is written to the profile * directory. Also tests that this allows BackupResource's with @@ -235,9 +262,10 @@ add_task(async function test_enable_encryption() { }); /** - * Tests that encryption cannot be enabled if it's already enabled. + * Tests that enabling encryption while it is already enabled changes the + * password. */ -add_task(async function test_already_enabled_encryption() { +add_task(async function test_change_encryption_password() { let bs = new BackupService(); let testProfilePath = await IOUtils.createUniqueDirectory( @@ -250,11 +278,18 @@ add_task(async function test_already_enabled_encryption() { let encState = await bs.loadEncryptionState(testProfilePath); Assert.ok(encState, "ArchiveEncryptionState is available."); + Assert.ok( + await backupServiceUsesPassword(bs, testProfilePath, TEST_PASSWORD), + "BackupService is using TEST_PASSWORD" + ); - await Assert.rejects( - bs.enableEncryption(TEST_PASSWORD, testProfilePath), - /already enabled/, - "It should not be possible to enable encryption if it's already enabled" + // Change the password. + await bs.enableEncryption(TEST_PASSWORD_ALT, testProfilePath); + encState = await bs.loadEncryptionState(testProfilePath); + Assert.ok(encState, "ArchiveEncryptionState is still available."); + Assert.ok( + await backupServiceUsesPassword(bs, testProfilePath, TEST_PASSWORD_ALT), + "BackupService is using TEST_PASSWORD_ALT" ); await IOUtils.remove(testProfilePath, { recursive: true }); @@ -313,6 +348,10 @@ add_task(async function test_disabling_encryption() { )), "Encryption state file should have been removed." ); + Assert.ok( + await backupServiceUsesPassword(bs, testProfilePath, null), + "BackupService should not use encryption." + ); await IOUtils.remove(testProfilePath, { recursive: true }); });