diff options
Diffstat (limited to '')
-rw-r--r-- | toolkit/components/extensions/parent/ext-privacy.js | 516 |
1 files changed, 516 insertions, 0 deletions
diff --git a/toolkit/components/extensions/parent/ext-privacy.js b/toolkit/components/extensions/parent/ext-privacy.js new file mode 100644 index 0000000000..1c4bf05ff1 --- /dev/null +++ b/toolkit/components/extensions/parent/ext-privacy.js @@ -0,0 +1,516 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +/* 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/. */ + +"use strict"; + +var { ExtensionPreferencesManager } = ChromeUtils.importESModule( + "resource://gre/modules/ExtensionPreferencesManager.sys.mjs" +); + +var { ExtensionError } = ExtensionUtils; +var { getSettingsAPI, getPrimedSettingsListener } = ExtensionPreferencesManager; + +const cookieSvc = Ci.nsICookieService; + +const getIntPref = p => Services.prefs.getIntPref(p, undefined); +const getBoolPref = p => Services.prefs.getBoolPref(p, undefined); + +const TLS_MIN_PREF = "security.tls.version.min"; +const TLS_MAX_PREF = "security.tls.version.max"; + +const cookieBehaviorValues = new Map([ + ["allow_all", cookieSvc.BEHAVIOR_ACCEPT], + ["reject_third_party", cookieSvc.BEHAVIOR_REJECT_FOREIGN], + ["reject_all", cookieSvc.BEHAVIOR_REJECT], + ["allow_visited", cookieSvc.BEHAVIOR_LIMIT_FOREIGN], + ["reject_trackers", cookieSvc.BEHAVIOR_REJECT_TRACKER], + [ + "reject_trackers_and_partition_foreign", + cookieSvc.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN, + ], +]); + +function isTLSMinVersionLowerOrEQThan(version) { + return ( + Services.prefs.getDefaultBranch("").getIntPref(TLS_MIN_PREF) <= version + ); +} + +const TLS_VERSIONS = [ + { version: 1, name: "TLSv1", settable: isTLSMinVersionLowerOrEQThan(1) }, + { version: 2, name: "TLSv1.1", settable: isTLSMinVersionLowerOrEQThan(2) }, + { version: 3, name: "TLSv1.2", settable: true }, + { version: 4, name: "TLSv1.3", settable: true }, +]; + +// Add settings objects for supported APIs to the preferences manager. +ExtensionPreferencesManager.addSetting("network.networkPredictionEnabled", { + permission: "privacy", + prefNames: [ + "network.predictor.enabled", + "network.prefetch-next", + "network.http.speculative-parallel-limit", + "network.dns.disablePrefetch", + ], + + setCallback(value) { + return { + "network.http.speculative-parallel-limit": value ? undefined : 0, + "network.dns.disablePrefetch": !value, + "network.predictor.enabled": value, + "network.prefetch-next": value, + }; + }, + + getCallback() { + return ( + getBoolPref("network.predictor.enabled") && + getBoolPref("network.prefetch-next") && + getIntPref("network.http.speculative-parallel-limit") > 0 && + !getBoolPref("network.dns.disablePrefetch") + ); + }, +}); + +ExtensionPreferencesManager.addSetting("network.globalPrivacyControl", { + permission: "privacy", + prefNames: ["privacy.globalprivacycontrol.enabled"], + readOnly: true, + + setCallback(value) { + return { + "privacy.globalprivacycontrol.enabled": value, + }; + }, + + getCallback() { + return getBoolPref("privacy.globalprivacycontrol.enabled"); + }, +}); + +ExtensionPreferencesManager.addSetting("network.httpsOnlyMode", { + permission: "privacy", + prefNames: [ + "dom.security.https_only_mode", + "dom.security.https_only_mode_pbm", + ], + readOnly: true, + + setCallback(value) { + let prefs = { + "dom.security.https_only_mode": false, + "dom.security.https_only_mode_pbm": false, + }; + + switch (value) { + case "always": + prefs["dom.security.https_only_mode"] = true; + break; + + case "private_browsing": + prefs["dom.security.https_only_mode_pbm"] = true; + break; + + case "never": + break; + } + + return prefs; + }, + + getCallback() { + if (getBoolPref("dom.security.https_only_mode")) { + return "always"; + } + if (getBoolPref("dom.security.https_only_mode_pbm")) { + return "private_browsing"; + } + return "never"; + }, +}); + +ExtensionPreferencesManager.addSetting("network.peerConnectionEnabled", { + permission: "privacy", + prefNames: ["media.peerconnection.enabled"], + + setCallback(value) { + return { [this.prefNames[0]]: value }; + }, + + getCallback() { + return getBoolPref("media.peerconnection.enabled"); + }, +}); + +ExtensionPreferencesManager.addSetting("network.webRTCIPHandlingPolicy", { + permission: "privacy", + prefNames: [ + "media.peerconnection.ice.default_address_only", + "media.peerconnection.ice.no_host", + "media.peerconnection.ice.proxy_only_if_behind_proxy", + "media.peerconnection.ice.proxy_only", + ], + + setCallback(value) { + let prefs = {}; + switch (value) { + case "default": + // All prefs are already set to be reset. + break; + + case "default_public_and_private_interfaces": + prefs["media.peerconnection.ice.default_address_only"] = true; + break; + + case "default_public_interface_only": + prefs["media.peerconnection.ice.default_address_only"] = true; + prefs["media.peerconnection.ice.no_host"] = true; + break; + + case "disable_non_proxied_udp": + prefs["media.peerconnection.ice.default_address_only"] = true; + prefs["media.peerconnection.ice.no_host"] = true; + prefs["media.peerconnection.ice.proxy_only_if_behind_proxy"] = true; + break; + + case "proxy_only": + prefs["media.peerconnection.ice.proxy_only"] = true; + break; + } + return prefs; + }, + + getCallback() { + if (getBoolPref("media.peerconnection.ice.proxy_only")) { + return "proxy_only"; + } + + let default_address_only = getBoolPref( + "media.peerconnection.ice.default_address_only" + ); + if (default_address_only) { + let no_host = getBoolPref("media.peerconnection.ice.no_host"); + if (no_host) { + if ( + getBoolPref("media.peerconnection.ice.proxy_only_if_behind_proxy") + ) { + return "disable_non_proxied_udp"; + } + return "default_public_interface_only"; + } + return "default_public_and_private_interfaces"; + } + + return "default"; + }, +}); + +ExtensionPreferencesManager.addSetting("services.passwordSavingEnabled", { + permission: "privacy", + prefNames: ["signon.rememberSignons"], + + setCallback(value) { + return { [this.prefNames[0]]: value }; + }, + + getCallback() { + return getBoolPref("signon.rememberSignons"); + }, +}); + +ExtensionPreferencesManager.addSetting("websites.cookieConfig", { + permission: "privacy", + prefNames: ["network.cookie.cookieBehavior"], + + setCallback(value) { + const cookieBehavior = cookieBehaviorValues.get(value.behavior); + + // Intentionally use Preferences.get("network.cookie.cookieBehavior") here + // to read the "real" preference value. + const needUpdate = + cookieBehavior !== getIntPref("network.cookie.cookieBehavior"); + if ( + needUpdate && + getBoolPref("privacy.firstparty.isolate") && + cookieBehavior === cookieSvc.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN + ) { + throw new ExtensionError( + `Invalid cookieConfig '${value.behavior}' when firstPartyIsolate is enabled` + ); + } + + if (typeof value.nonPersistentCookies === "boolean") { + Cu.reportError( + "'nonPersistentCookies' has been deprecated and it has no effect anymore." + ); + } + + return { + "network.cookie.cookieBehavior": cookieBehavior, + }; + }, + + getCallback() { + let prefValue = getIntPref("network.cookie.cookieBehavior"); + return { + behavior: Array.from(cookieBehaviorValues.entries()).find( + entry => entry[1] === prefValue + )[0], + // Bug 1754924 - this property is now deprecated. + nonPersistentCookies: false, + }; + }, +}); + +ExtensionPreferencesManager.addSetting("websites.firstPartyIsolate", { + permission: "privacy", + prefNames: ["privacy.firstparty.isolate"], + + setCallback(value) { + // Intentionally use Preferences.get("network.cookie.cookieBehavior") here + // to read the "real" preference value. + const cookieBehavior = getIntPref("network.cookie.cookieBehavior"); + + const needUpdate = value !== getBoolPref("privacy.firstparty.isolate"); + if ( + needUpdate && + value && + cookieBehavior === cookieSvc.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN + ) { + const behavior = Array.from(cookieBehaviorValues.entries()).find( + entry => entry[1] === cookieBehavior + )[0]; + throw new ExtensionError( + `Can't enable firstPartyIsolate when cookieBehavior is '${behavior}'` + ); + } + + return { [this.prefNames[0]]: value }; + }, + + getCallback() { + return getBoolPref("privacy.firstparty.isolate"); + }, +}); + +ExtensionPreferencesManager.addSetting("websites.hyperlinkAuditingEnabled", { + permission: "privacy", + prefNames: ["browser.send_pings"], + + setCallback(value) { + return { [this.prefNames[0]]: value }; + }, + + getCallback() { + return getBoolPref("browser.send_pings"); + }, +}); + +ExtensionPreferencesManager.addSetting("websites.referrersEnabled", { + permission: "privacy", + prefNames: ["network.http.sendRefererHeader"], + + // Values for network.http.sendRefererHeader: + // 0=don't send any, 1=send only on clicks, 2=send on image requests as well + // http://searchfox.org/mozilla-central/rev/61054508641ee76f9c49bcf7303ef3cfb6b410d2/modules/libpref/init/all.js#1585 + setCallback(value) { + return { [this.prefNames[0]]: value ? 2 : 0 }; + }, + + getCallback() { + return getIntPref("network.http.sendRefererHeader") !== 0; + }, +}); + +ExtensionPreferencesManager.addSetting("websites.resistFingerprinting", { + permission: "privacy", + prefNames: ["privacy.resistFingerprinting"], + + setCallback(value) { + return { [this.prefNames[0]]: value }; + }, + + getCallback() { + return getBoolPref("privacy.resistFingerprinting"); + }, +}); + +ExtensionPreferencesManager.addSetting("websites.trackingProtectionMode", { + permission: "privacy", + prefNames: [ + "privacy.trackingprotection.enabled", + "privacy.trackingprotection.pbmode.enabled", + ], + + setCallback(value) { + // Default to private browsing. + let prefs = { + "privacy.trackingprotection.enabled": false, + "privacy.trackingprotection.pbmode.enabled": true, + }; + + switch (value) { + case "private_browsing": + break; + + case "always": + prefs["privacy.trackingprotection.enabled"] = true; + break; + + case "never": + prefs["privacy.trackingprotection.pbmode.enabled"] = false; + break; + } + + return prefs; + }, + + getCallback() { + if (getBoolPref("privacy.trackingprotection.enabled")) { + return "always"; + } else if (getBoolPref("privacy.trackingprotection.pbmode.enabled")) { + return "private_browsing"; + } + return "never"; + }, +}); + +ExtensionPreferencesManager.addSetting("network.tlsVersionRestriction", { + permission: "privacy", + prefNames: [TLS_MIN_PREF, TLS_MAX_PREF], + + setCallback(value) { + function tlsStringToVersion(string) { + const version = TLS_VERSIONS.find(a => a.name === string); + if (version && version.settable) { + return version.version; + } + + throw new ExtensionError( + `Setting TLS version ${string} is not allowed for security reasons.` + ); + } + + const prefs = {}; + + if (value.minimum) { + prefs[TLS_MIN_PREF] = tlsStringToVersion(value.minimum); + } + + if (value.maximum) { + prefs[TLS_MAX_PREF] = tlsStringToVersion(value.maximum); + } + + // If minimum has passed and it's greater than the max value. + if (prefs[TLS_MIN_PREF]) { + const max = prefs[TLS_MAX_PREF] || getIntPref(TLS_MAX_PREF); + if (max < prefs[TLS_MIN_PREF]) { + throw new ExtensionError( + `Setting TLS min version grater than the max version is not allowed.` + ); + } + } + + // If maximum has passed and it's lower than the min value. + else if (prefs[TLS_MAX_PREF]) { + const min = getIntPref(TLS_MIN_PREF); + if (min > prefs[TLS_MAX_PREF]) { + throw new ExtensionError( + `Setting TLS max version lower than the min version is not allowed.` + ); + } + } + + return prefs; + }, + + getCallback() { + function tlsVersionToString(pref) { + const value = getIntPref(pref); + const version = TLS_VERSIONS.find(a => a.version === value); + if (version) { + return version.name; + } + return "unknown"; + } + + return { + minimum: tlsVersionToString(TLS_MIN_PREF), + maximum: tlsVersionToString(TLS_MAX_PREF), + }; + }, + + validate(extension) { + if (!extension.isPrivileged) { + throw new ExtensionError( + "tlsVersionRestriction can be set by privileged extensions only." + ); + } + }, +}); + +this.privacy = class extends ExtensionAPI { + primeListener(event, fire) { + let { extension } = this; + let listener = getPrimedSettingsListener({ + extension, + name: event, + }); + return listener(fire); + } + + getAPI(context) { + function makeSettingsAPI(name) { + return getSettingsAPI({ + context, + module: "privacy", + name, + }); + } + + return { + privacy: { + network: { + networkPredictionEnabled: makeSettingsAPI( + "network.networkPredictionEnabled" + ), + globalPrivacyControl: makeSettingsAPI("network.globalPrivacyControl"), + httpsOnlyMode: makeSettingsAPI("network.httpsOnlyMode"), + peerConnectionEnabled: makeSettingsAPI( + "network.peerConnectionEnabled" + ), + webRTCIPHandlingPolicy: makeSettingsAPI( + "network.webRTCIPHandlingPolicy" + ), + tlsVersionRestriction: makeSettingsAPI( + "network.tlsVersionRestriction" + ), + }, + + services: { + passwordSavingEnabled: makeSettingsAPI( + "services.passwordSavingEnabled" + ), + }, + + websites: { + cookieConfig: makeSettingsAPI("websites.cookieConfig"), + firstPartyIsolate: makeSettingsAPI("websites.firstPartyIsolate"), + hyperlinkAuditingEnabled: makeSettingsAPI( + "websites.hyperlinkAuditingEnabled" + ), + referrersEnabled: makeSettingsAPI("websites.referrersEnabled"), + resistFingerprinting: makeSettingsAPI( + "websites.resistFingerprinting" + ), + trackingProtectionMode: makeSettingsAPI( + "websites.trackingProtectionMode" + ), + }, + }, + }; + } +}; |