1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
8 import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";
9 import { TelemetryController } from "resource://gre/modules/TelemetryController.sys.mjs";
11 const PREF_SSL_IMPACT_ROOTS = [
12 "security.tls.version.",
19 ChromeUtils.defineESModuleGetters(lazy, {
20 BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
21 HomePage: "resource:///modules/HomePage.sys.mjs",
24 class CaptivePortalObserver {
27 Services.obs.addObserver(this, "captive-portal-login-abort");
28 Services.obs.addObserver(this, "captive-portal-login-success");
32 Services.obs.removeObserver(this, "captive-portal-login-abort");
33 Services.obs.removeObserver(this, "captive-portal-login-success");
36 observe(aSubject, aTopic, aData) {
38 case "captive-portal-login-abort":
39 case "captive-portal-login-success":
40 // Send a message to the content when a captive portal is freed
41 // so that error pages can refresh themselves.
42 this.actor.sendAsyncMessage("AboutNetErrorCaptivePortalFreed");
48 export class NetErrorParent extends JSWindowActorParent {
51 this.captivePortalObserver = new CaptivePortalObserver(this);
55 if (this.captivePortalObserver) {
56 this.captivePortalObserver.stop();
61 return this.browsingContext.top.embedderElement;
64 hasChangedCertPrefs() {
65 let prefSSLImpact = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
66 return prefs.concat(Services.prefs.getChildList(root));
68 for (let prefName of prefSSLImpact) {
69 if (Services.prefs.prefHasUserValue(prefName)) {
77 async ReportBlockingError(bcID, scheme, host, port, path, xfoAndCspInfo) {
78 // For reporting X-Frame-Options error and CSP: frame-ancestors errors, We
79 // are collecting 4 pieces of information.
80 // 1. The X-Frame-Options in the response header.
81 // 2. The CSP: frame-ancestors in the response header.
82 // 3. The URI of the frame who triggers this error.
83 // 4. The top-level URI which loads the frame.
85 // We will exclude the query strings from the reporting URIs.
87 // More details about the data we send can be found in
88 // https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/xfocsp-error-report-ping.html
91 let topBC = BrowsingContext.get(bcID).top;
92 let topURI = topBC.currentWindowGlobal.documentURI;
94 // Get the URLs without query strings.
95 let frame_uri = `${scheme}://${host}${port == -1 ? "" : ":" + port}${path}`;
96 let top_uri = `${topURI.scheme}://${topURI.hostPort}${topURI.filePath}`;
98 TelemetryController.submitExternalPing(
99 "xfocsp-error-report",
102 frame_hostname: host,
103 top_hostname: topURI.host,
107 { addClientId: false, addEnvironment: false }
112 * Return the default start page for the cases when the user's own homepage is
113 * infected, so we can get them somewhere safe.
115 getDefaultHomePage(win) {
118 !PrivateBrowsingUtils.isWindowPrivate(win) &&
119 AppConstants.MOZ_BUILD_APP == "browser"
121 url = lazy.HomePage.getDefault();
123 url ||= win.BROWSER_NEW_TAB_URL || "about:blank";
125 // If url is a pipe-delimited set of pages, just take the first one.
126 if (url.includes("|")) {
127 url = url.split("|")[0];
133 * Re-direct the browser to the previous page or a known-safe page if no
134 * previous page is found in history. This function is used when the user
135 * browses to a secure page with certificate issues and is presented with
136 * about:certerror. The "Go Back" button should take the user to the previous
137 * or a default start page so that even when their own homepage is on a server
138 * that has certificate errors, we can get them somewhere safe.
140 goBackFromErrorPage(browser) {
141 if (!browser.canGoBack) {
142 // If the unsafe page is the first or the only one in history, go to the
144 browser.fixupAndLoadURIString(
145 this.getDefaultHomePage(browser.ownerGlobal),
148 Services.scriptSecurityManager.getSystemPrincipal(),
157 * This function does a canary request to a reliable, maintained endpoint, in
158 * order to help network code detect a system-wide man-in-the-middle.
161 // If we already have a mitm canary issuer stored, then don't bother with the
162 // extra request. This will be cleared on every update ping.
163 if (Services.prefs.getStringPref("security.pki.mitm_canary_issuer", null)) {
167 let url = Services.prefs.getStringPref(
168 "security.certerrors.mitm.priming.endpoint"
170 let request = new XMLHttpRequest({ mozAnon: true });
171 request.open("HEAD", url);
172 request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
173 request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
175 request.addEventListener("error", event => {
176 // Make sure the user is still on the cert error page.
177 if (!browser.documentURI.spec.startsWith("about:certerror")) {
181 let secInfo = request.channel.securityInfo;
182 if (secInfo.errorCodeString != "SEC_ERROR_UNKNOWN_ISSUER") {
186 // When we get to this point there's already something deeply wrong, it's very likely
187 // that there is indeed a system-wide MitM.
188 if (secInfo.serverCert && secInfo.serverCert.issuerName) {
189 // Grab the issuer of the certificate used in the exchange and store it so that our
190 // network-level MitM detection code has a comparison baseline.
191 Services.prefs.setStringPref(
192 "security.pki.mitm_canary_issuer",
193 secInfo.serverCert.issuerName
196 // MitM issues are sometimes caused by software not registering their root certs in the
197 // Firefox root store. We might opt for using third party roots from the system root store.
199 Services.prefs.getBoolPref(
200 "security.certerrors.mitm.auto_enable_enterprise_roots"
204 !Services.prefs.getBoolPref("security.enterprise_roots.enabled")
206 // Loading enterprise roots happens on a background thread, so wait for import to finish.
207 lazy.BrowserUtils.promiseObserved(
208 "psm:enterprise-certs-imported"
210 if (browser.documentURI.spec.startsWith("about:certerror")) {
215 Services.prefs.setBoolPref(
216 "security.enterprise_roots.enabled",
219 // Record that this pref was automatically set.
220 Services.prefs.setBoolPref(
221 "security.enterprise_roots.auto-enabled",
226 // Need to reload the page to make sure network code picks up the canary issuer pref.
235 displayOfflineSupportPage(supportPageSlug) {
236 const AVAILABLE_PAGES = ["connection-not-secure", "time-errors"];
237 if (!AVAILABLE_PAGES.includes(supportPageSlug)) {
239 `[Not supported] Offline support is not yet available for ${supportPageSlug} errors.`
244 let offlinePagePath = `chrome://global/content/neterror/supportpages/${supportPageSlug}.html`;
245 let triggeringPrincipal =
246 Services.scriptSecurityManager.getSystemPrincipal();
247 this.browser.loadURI(Services.io.newURI(offlinePagePath), {
252 receiveMessage(message) {
253 switch (message.name) {
254 case "Browser:EnableOnlineMode":
255 // Reset network state and refresh the page.
256 Services.io.offline = false;
257 this.browser.reload();
259 case "Browser:OpenCaptivePortalPage":
260 this.browser.ownerGlobal.CaptivePortalWatcher.ensureCaptivePortalTab();
262 case "Browser:PrimeMitm":
263 this.primeMitm(this.browser);
265 case "Browser:ResetEnterpriseRootsPref":
266 Services.prefs.clearUserPref("security.enterprise_roots.enabled");
267 Services.prefs.clearUserPref("security.enterprise_roots.auto-enabled");
269 case "Browser:ResetSSLPreferences":
270 let prefSSLImpact = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
271 return prefs.concat(Services.prefs.getChildList(root));
273 for (let prefName of prefSSLImpact) {
274 Services.prefs.clearUserPref(prefName);
276 this.browser.reload();
278 case "Browser:SSLErrorGoBack":
279 this.goBackFromErrorPage(this.browser);
281 case "GetChangedCertPrefs":
282 let hasChangedCertPrefs = this.hasChangedCertPrefs();
283 this.sendAsyncMessage("HasChangedCertPrefs", {
287 case "ReportBlockingError":
288 this.ReportBlockingError(
289 this.browsingContext.id,
294 message.data.xfoAndCspInfo
297 case "DisplayOfflineSupportPage":
298 this.displayOfflineSupportPage(message.data.supportPageSlug);
300 case "Browser:CertExceptionError":
301 switch (message.data.elementId) {
302 case "viewCertificate": {
303 let certs = message.data.failedCertChain.map(certBase64 =>
304 encodeURIComponent(certBase64)
306 let certsStringURL = certs.map(elem => `cert=${elem}`);
307 certsStringURL = certsStringURL.join("&");
308 let url = `about:certificate?${certsStringURL}`;
310 let window = this.browser.ownerGlobal;
311 if (AppConstants.MOZ_BUILD_APP === "browser") {
312 window.switchToTabHavingURI(url, true, {});
314 window.open(url, "_blank");
320 case "Browser:AddTRRExcludedDomain":
321 let domain = message.data.hostname;
322 let excludedDomains = Services.prefs.getStringPref(
323 "network.trr.excluded-domains"
325 excludedDomains += `, ${domain}`;
326 Services.prefs.setStringPref(
327 "network.trr.excluded-domains",
331 case "OpenTRRPreferences":
332 let browser = this.browsingContext.top.embedderElement;
337 let win = browser.ownerGlobal;
338 win.openPreferences("privacy-doh");