Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 36 additions & 43 deletions browser/components/BrowserGlue.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1119,72 +1119,65 @@ BrowserGlue.prototype = {
false
),
task: async () => {
// Get the primary secret from the console backend
// The API returns { data: "secret_value" }
// A managed browser that cannot unlock its encrypted storage must not
// keep running: it would prompt for a primary secret the user does not
// know, or operate against a profile it cannot decrypt. Fail the launch
// with a dedicated exit code (Bug 2021342), mirroring the launcher-side
// abort in FeltProcessParent.
const fail = (msg, e) => {
console.error(
`EnterpriseStorageEncryption.load: ${msg}${e ? ": " + e : ""}`
);
Services.startup.quit(
Ci.nsIAppStartup.eForceQuit,
Ci.nsIFelt.FeltEncryptionExitCode_SdrTokenUnlockFailed
);
};

// The API returns { data: "secret_value" }.
let primarySecret;
try {
const payload = await lazy.ConsoleClient.getPrimarySecret();
primarySecret = payload.data;
if (!primarySecret) {
console.error(
"EnterpriseStorageEncryption.load: No data field in payload:",
payload
);
return;
}
primarySecret = (await lazy.ConsoleClient.getPrimarySecret()).data;
} catch (e) {
console.error(
"EnterpriseStorageEncryption.load: Failed to get primary secret:",
e
);
fail("Failed to get primary secret", e);
return;
}
if (!primarySecret) {
fail("No primary secret in payload");
return;
}

// Load the PK11 token
let pk11token;
try {
pk11token = Cc[
"@mozilla.org/security/internalkeytoken;1"
].createInstance(Ci.nsIPKCS11Token);
} catch (e) {
console.error(
"EnterpriseStorageEncryption.load: Error getting PK11 token: " + e
);
fail("Error getting PK11 token", e);
return;
}

// Check if the PK11 token needs initialization
// Ensure the internal token's password is the primarySecret.
if (!pk11token.hasPassword) {
// Token doesn't need login (empty password), set it to primarySecret
try {
await pk11token.changePassword("", primarySecret);
} catch (e) {
console.error(
"EnterpriseStorageEncryption.load: Failed to change password from empty to primarySecret: " +
e
);
}
} else {
// Token needs login - verify the password matches primarySecret
let isPasswordValid;
try {
let sdr = Cc["@mozilla.org/security/sdr;1"].getService(
Ci.nsISecretDecoderRing
);
isPasswordValid = sdr.login(primarySecret);
} catch (e) {
console.error(
"EnterpriseStorageEncryption.load: Error logging into the Secret Decoder Ring with the primary secret: " +
e
);
fail("Failed to set the primary secret on the token", e);
return;
}
}

if (!isPasswordValid) {
console.error(
"EnterpriseStorageEncryption.load: Password against the PK11 token is not valid"
);
// changePassword does not authenticate the session, so log in
// explicitly and verify.
try {
const sdr = Cc["@mozilla.org/security/sdr;1"].getService(
Ci.nsISecretDecoderRing
);
if (!sdr.login(primarySecret) || !pk11token.isLoggedIn) {
fail("Internal token not logged in after unlock");
}
} catch (e) {
fail("SDR login failed", e);
}
},
},
Expand Down
20 changes: 19 additions & 1 deletion security/manager/ssl/SecretDecoderRing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,22 @@ SecretDecoderRing::Login(const nsACString& password, bool* success) {
return NS_OK;
}

// The enterprise storage-encryption feature unlocks the internal token once at
// startup with a console-supplied primarySecret the user does not know. While
// that feature manages the primary password, the ClearData CLEAR_AUTH_TOKENS
// path (LogoutAndTeardown, below) must not de-authenticate the internal token,
// or later SDR use would prompt for a password the user cannot supply
// (Bug 2021342). Non-token teardown (TLS cache, connection pruning) still runs.
// The reauth path enforces the same invariant independently in
// LoginHelper.sys.mjs, using the identical MOZ_ENTERPRISE + pref gate.
static bool EnterpriseManagesPrimaryPassword() {
#if defined(MOZ_ENTERPRISE)
return mozilla::StaticPrefs::security_storage_encryption_enabled();
#else
return false;
#endif
}

NS_IMETHODIMP
SecretDecoderRing::Logout() {
PK11_LogoutAll();
Expand All @@ -317,7 +333,9 @@ SecretDecoderRing::Logout() {

NS_IMETHODIMP
SecretDecoderRing::LogoutAndTeardown() {
PK11_LogoutAll();
if (!EnterpriseManagesPrimaryPassword()) {
PK11_LogoutAll();
Comment thread
beurdouche marked this conversation as resolved.
}
nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(NS_NSSCOMPONENT_CID));
if (!nssComponent) {
return NS_ERROR_NOT_AVAILABLE;
Expand Down
47 changes: 47 additions & 0 deletions security/manager/ssl/tests/unit/test_sdr_enterprise_logout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/
"use strict";

// Bug 2021342: while the enterprise storage-encryption feature manages the
// internal token's primary password (a console-supplied secret the user does
// not know), SecretDecoderRing::LogoutAndTeardown must not log the token out --
// otherwise later SDR use would prompt for a password the user cannot supply.
// This test is enterprise-gated (run-if = ["enterprise"]) because the guard is
// #if defined(MOZ_ENTERPRISE); on a non-enterprise build the token would log
// out and these assertions would correctly fail.

do_get_profile();

add_task(async function test_enterprise_logout_keeps_token_unlocked() {
Services.prefs.setBoolPref("security.storage.encryption.enabled", true);
registerCleanupFunction(() =>
Services.prefs.clearUserPref("security.storage.encryption.enabled")
);

const secret = "primary-secret";
let token = Cc["@mozilla.org/security/internalkeytoken;1"].createInstance(
Ci.nsIPKCS11Token
);
Assert.ok(!token.isLoggedIn, "token starts logged out");
await token.changePassword("", secret);

let sdr = Cc["@mozilla.org/security/sdr;1"].getService(
Ci.nsISecretDecoderRing
);
Assert.ok(sdr.login(secret), "SDR login with the primary secret succeeds");
Assert.ok(token.isLoggedIn, "token is logged in after the initial unlock");

// The CLEAR_AUTH_TOKENS path (AuthTokensCleaner -> logoutAndTeardown). The
// non-token teardown still runs; only PK11_LogoutAll is suppressed.
sdr.logoutAndTeardown();
Assert.ok(
token.isLoggedIn,
"enterprise-managed token stays logged in through logoutAndTeardown"
);

// Logout() is intentionally left unguarded (it has no production caller);
// only LogoutAndTeardown() is guarded. Verify the narrowing so a future
// change that re-adds the guard to Logout() is caught.
sdr.logout();
Assert.ok(!token.isLoggedIn, "logout() still logs the token out");
});
4 changes: 4 additions & 0 deletions security/manager/ssl/tests/unit/xpcshell.toml
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,10 @@ run-sequentially = ["true"] # hardcoded ports

["test_sdr.js"]

["test_sdr_enterprise_logout.js"]
prefs = ["security.storage.encryption.sqlite.enabled=false"]
run-if = ["enterprise"] # MOZ_ENTERPRISE-only; the SDR logout guard is compiled out otherwise

["test_sdr_preexisting.js"]
prefs = ["security.storage.encryption.sqlite.enabled=false"]
run-if = [
Expand Down
9 changes: 6 additions & 3 deletions toolkit/components/felt/Felt.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,13 @@ export class Felt {

case "FeltParent:FirefoxLaunchFailure": {
Services.felt.makeBackgroundProcess(false);
const errorClassByType = {
primarySecret: "felt-error-primary-secret",
sdrTokenUnlockFailed: "felt-error-sdr-token-unlock-failed",
};
const errorClass =
message.data?.errorType === "primarySecret"
? "felt-error-primary-secret"
: "felt-browser-error-launch-failure";
errorClassByType[message.data?.errorType] ??
"felt-browser-error-launch-failure";
this.showWindow(errorClass);
break;
}
Expand Down
15 changes: 15 additions & 0 deletions toolkit/components/felt/content/FeltProcessParent.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,21 @@ export class FeltProcessParent extends JSProcessActorParent {
* again or to inform the user of the set of crashes.
*/
handleRestartAfterAbnormalExit() {
if (
this.proc.exitCode ===
Ci.nsIFelt.FeltEncryptionExitCode_SdrTokenUnlockFailed
) {
// The profile could not be unlocked (missing or rejected primary secret).
// Restarting cannot fix a wrong or rotated secret, so surface a clear
// error instead of counting this as a crash (Bug 2021342).
this.abnormalExitCounter = 0;
this.abnormalExitFirstTime = 0;
Services.cpmm.sendAsyncMessage("FeltParent:FirefoxLaunchFailure", {
errorType: "sdrTokenUnlockFailed",
});
return;
}

if (this.proc.exitCode === Ci.nsIFelt.FeltEncryptionExitCode_Delete) {
// Firefox encryption explicitely reported to delete the profile folder
// The profile service should do it but it may be incomplete depending
Expand Down
6 changes: 6 additions & 0 deletions toolkit/components/felt/content/felt.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@
type="error"
dismissable="true"
></moz-message-bar>
<moz-message-bar
class="felt-error-sdr-token-unlock-failed is-hidden"
data-l10n-id="felt-error-sdr-token-unlock-failed"
type="error"
dismissable="true"
></moz-message-bar>
<moz-message-bar
class="felt-browser-error-no-network is-hidden"
data-l10n-id="felt-browser-error-no-network"
Expand Down
5 changes: 5 additions & 0 deletions toolkit/components/felt/rust/nsIFelt.idl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
interface nsIFelt : nsISupports {
const unsigned long FeltEncryptionExitCode_Archive = 0x93;
const unsigned long FeltEncryptionExitCode_Delete = 0x94;
// The spawned browser could not unlock its profile: the internal (SDR) token
// could not be logged in (no primarySecret, or a rejected/rotated secret).
// Exit with this code rather than run against a profile it cannot decrypt
// (Bug 2021342).
const unsigned long FeltEncryptionExitCode_SdrTokenUnlockFailed = 0x95;

[must_use]
ACString oneShotIpcServer();
Expand Down
3 changes: 3 additions & 0 deletions toolkit/locales/en-US/toolkit/enterprise/felt.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ felt-browser-error-launch-failure =
felt-error-primary-secret =
.heading = { -brand-short-name } cannot start securely
.message = Your secure profile key could not be retrieved. Please try again, or contact your administrator if the problem persists.
felt-error-sdr-token-unlock-failed =
.heading = { -brand-short-name } cannot unlock your profile
.message = Your secure profile could not be unlocked. Please try again, or contact your administrator if the problem persists.

## Logout messages

Expand Down