diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /browser/components/aboutlogins/LoginBreaches.sys.mjs | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | browser/components/aboutlogins/LoginBreaches.sys.mjs | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/browser/components/aboutlogins/LoginBreaches.sys.mjs b/browser/components/aboutlogins/LoginBreaches.sys.mjs new file mode 100644 index 0000000000..bd7a8cdf66 --- /dev/null +++ b/browser/components/aboutlogins/LoginBreaches.sys.mjs @@ -0,0 +1,176 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Manages breach alerts for saved logins using data from Firefox Monitor via + * RemoteSettings. + */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs", + RemoteSettings: "resource://services-settings/remote-settings.sys.mjs", + RemoteSettingsClient: + "resource://services-settings/RemoteSettingsClient.sys.mjs", +}); + +export const LoginBreaches = { + REMOTE_SETTINGS_COLLECTION: "fxmonitor-breaches", + + async update(breaches = null) { + const logins = await lazy.LoginHelper.getAllUserFacingLogins(); + await this.getPotentialBreachesByLoginGUID(logins, breaches); + }, + + /** + * Return a Map of login GUIDs to a potential breach affecting that login + * by considering only breaches affecting passwords. + * + * This only uses the breach `Domain` and `timePasswordChanged` to determine + * if a login may be breached which means it may contain false-positives if + * login timestamps are incorrect, the user didn't save their password change + * in Firefox, or the breach didn't contain all accounts, etc. As a result, + * consumers should avoid making stronger claims than the data supports. + * + * @param {nsILoginInfo[]} logins Saved logins to check for potential breaches. + * @param {object[]} [breaches = null] Only ones involving passwords will be used. + * @returns {Map} with a key for each login GUID potentially in a breach. + */ + async getPotentialBreachesByLoginGUID(logins, breaches = null) { + const breachesByLoginGUID = new Map(); + if (!breaches) { + try { + breaches = await lazy + .RemoteSettings(this.REMOTE_SETTINGS_COLLECTION) + .get(); + } catch (ex) { + if (ex instanceof lazy.RemoteSettingsClient.UnknownCollectionError) { + lazy.log.warn( + "Could not get Remote Settings collection.", + this.REMOTE_SETTINGS_COLLECTION, + ex + ); + return breachesByLoginGUID; + } + throw ex; + } + } + const BREACH_ALERT_URL = Services.prefs.getStringPref( + "signon.management.page.breachAlertUrl" + ); + const baseBreachAlertURL = new URL(BREACH_ALERT_URL); + + await Services.logins.initializationPromise; + const storageJSON = + Services.logins.wrappedJSObject._storage.wrappedJSObject; + const dismissedBreachAlertsByLoginGUID = + storageJSON.getBreachAlertDismissalsByLoginGUID(); + + // Determine potentially breached logins by checking their origin and the last time + // they were changed. It's important to note here that we are NOT considering the + // username and password of that login. + for (const login of logins) { + let loginHost; + try { + // nsIURI.host can throw if the URI scheme doesn't have a host. + loginHost = Services.io.newURI(login.origin).host; + } catch { + continue; + } + for (const breach of breaches) { + if ( + !breach.Domain || + !Services.eTLD.hasRootDomain(loginHost, breach.Domain) || + !this._breachInvolvedPasswords(breach) || + !this._breachWasAfterPasswordLastChanged(breach, login) + ) { + continue; + } + + if (!storageJSON.isPotentiallyVulnerablePassword(login)) { + storageJSON.addPotentiallyVulnerablePassword(login); + } + + if ( + this._breachAlertIsDismissed( + login, + breach, + dismissedBreachAlertsByLoginGUID + ) + ) { + continue; + } + + let breachAlertURL = new URL(breach.Name, baseBreachAlertURL); + breachAlertURL.searchParams.set("utm_source", "firefox-desktop"); + breachAlertURL.searchParams.set("utm_medium", "referral"); + breachAlertURL.searchParams.set("utm_campaign", "about-logins"); + breachAlertURL.searchParams.set("utm_content", "about-logins"); + breach.breachAlertURL = breachAlertURL.href; + breachesByLoginGUID.set(login.guid, breach); + } + } + Services.telemetry.scalarSet( + "pwmgr.potentially_breached_passwords", + breachesByLoginGUID.size + ); + return breachesByLoginGUID; + }, + + /** + * Return information about logins using passwords that were potentially in a + * breach. + * @see the caveats in the documentation for `getPotentialBreachesByLoginGUID`. + * + * @param {nsILoginInfo[]} logins to check the passwords of. + * @returns {Map} from login GUID to `true` for logins that have a password + * that may be vulnerable. + */ + getPotentiallyVulnerablePasswordsByLoginGUID(logins) { + const vulnerablePasswordsByLoginGUID = new Map(); + const storageJSON = + Services.logins.wrappedJSObject._storage.wrappedJSObject; + for (const login of logins) { + if (storageJSON.isPotentiallyVulnerablePassword(login)) { + vulnerablePasswordsByLoginGUID.set(login.guid, true); + } + } + return vulnerablePasswordsByLoginGUID; + }, + + async clearAllPotentiallyVulnerablePasswords() { + await Services.logins.initializationPromise; + const storageJSON = + Services.logins.wrappedJSObject._storage.wrappedJSObject; + storageJSON.clearAllPotentiallyVulnerablePasswords(); + }, + + _breachAlertIsDismissed(login, breach, dismissedBreachAlerts) { + const breachAddedDate = new Date(breach.AddedDate).getTime(); + const breachAlertIsDismissed = + dismissedBreachAlerts[login.guid] && + dismissedBreachAlerts[login.guid].timeBreachAlertDismissed > + breachAddedDate; + return breachAlertIsDismissed; + }, + + _breachInvolvedPasswords(breach) { + return ( + breach.hasOwnProperty("DataClasses") && + breach.DataClasses.includes("Passwords") + ); + }, + + _breachWasAfterPasswordLastChanged(breach, login) { + const breachDate = new Date(breach.BreachDate).getTime(); + return login.timePasswordChanged < breachDate; + }, +}; + +XPCOMUtils.defineLazyGetter(lazy, "log", () => { + return lazy.LoginHelper.createLogger("LoginBreaches"); +}); |