Bug 1888590 - Mark some subtests on trusted-types-event-handlers.html as failing...
[gecko.git] / toolkit / actors / NetErrorParent.sys.mjs
blob071529976a5418559a19b29099929e59275f2cad
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.",
13   "security.ssl3.",
14   "security.tls13.",
17 const lazy = {};
19 ChromeUtils.defineESModuleGetters(lazy, {
20   BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
21   HomePage: "resource:///modules/HomePage.sys.mjs",
22 });
24 class CaptivePortalObserver {
25   constructor(actor) {
26     this.actor = actor;
27     Services.obs.addObserver(this, "captive-portal-login-abort");
28     Services.obs.addObserver(this, "captive-portal-login-success");
29   }
31   stop() {
32     Services.obs.removeObserver(this, "captive-portal-login-abort");
33     Services.obs.removeObserver(this, "captive-portal-login-success");
34   }
36   observe(aSubject, aTopic) {
37     switch (aTopic) {
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");
43         break;
44     }
45   }
48 export class NetErrorParent extends JSWindowActorParent {
49   constructor() {
50     super();
51     this.captivePortalObserver = new CaptivePortalObserver(this);
52   }
54   didDestroy() {
55     if (this.captivePortalObserver) {
56       this.captivePortalObserver.stop();
57     }
58   }
60   get browser() {
61     return this.browsingContext.top.embedderElement;
62   }
64   hasChangedCertPrefs() {
65     let prefSSLImpact = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
66       return prefs.concat(Services.prefs.getChildList(root));
67     }, []);
68     for (let prefName of prefSSLImpact) {
69       if (Services.prefs.prefHasUserValue(prefName)) {
70         return true;
71       }
72     }
74     return false;
75   }
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.
84     //
85     // We will exclude the query strings from the reporting URIs.
86     //
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
89     //
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",
100       {
101         ...xfoAndCspInfo,
102         frame_hostname: host,
103         top_hostname: topURI.host,
104         frame_uri,
105         top_uri,
106       },
107       { addClientId: false, addEnvironment: false }
108     );
109   }
111   /**
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.
114    */
115   getDefaultHomePage(win) {
116     let url;
117     if (
118       !PrivateBrowsingUtils.isWindowPrivate(win) &&
119       AppConstants.MOZ_BUILD_APP == "browser"
120     ) {
121       url = lazy.HomePage.getDefault();
122     }
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];
128     }
129     return url;
130   }
132   /**
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.
139    */
140   goBackFromErrorPage(browser) {
141     if (!browser.canGoBack) {
142       // If the unsafe page is the first or the only one in history, go to the
143       // start page.
144       browser.fixupAndLoadURIString(
145         this.getDefaultHomePage(browser.ownerGlobal),
146         {
147           triggeringPrincipal:
148             Services.scriptSecurityManager.getSystemPrincipal(),
149         }
150       );
151     } else {
152       browser.goBack();
153     }
154   }
156   /**
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.
159    */
160   primeMitm(browser) {
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)) {
164       return;
165     }
167     let url = Services.prefs.getStringPref(
168       "security.certerrors.mitm.priming.endpoint"
169     );
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", () => {
176       // Make sure the user is still on the cert error page.
177       if (!browser.documentURI.spec.startsWith("about:certerror")) {
178         return;
179       }
181       let secInfo = request.channel.securityInfo;
182       if (secInfo.errorCodeString != "SEC_ERROR_UNKNOWN_ISSUER") {
183         return;
184       }
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
194         );
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.
198         if (
199           Services.prefs.getBoolPref(
200             "security.certerrors.mitm.auto_enable_enterprise_roots"
201           )
202         ) {
203           if (
204             !Services.prefs.getBoolPref("security.enterprise_roots.enabled")
205           ) {
206             // Loading enterprise roots happens on a background thread, so wait for import to finish.
207             lazy.BrowserUtils.promiseObserved(
208               "psm:enterprise-certs-imported"
209             ).then(() => {
210               if (browser.documentURI.spec.startsWith("about:certerror")) {
211                 browser.reload();
212               }
213             });
215             Services.prefs.setBoolPref(
216               "security.enterprise_roots.enabled",
217               true
218             );
219             // Record that this pref was automatically set.
220             Services.prefs.setBoolPref(
221               "security.enterprise_roots.auto-enabled",
222               true
223             );
224           }
225         } else {
226           // Need to reload the page to make sure network code picks up the canary issuer pref.
227           browser.reload();
228         }
229       }
230     });
232     request.send(null);
233   }
235   displayOfflineSupportPage(supportPageSlug) {
236     const AVAILABLE_PAGES = ["connection-not-secure", "time-errors"];
237     if (!AVAILABLE_PAGES.includes(supportPageSlug)) {
238       console.log(
239         `[Not supported] Offline support is not yet available for ${supportPageSlug} errors.`
240       );
241       return;
242     }
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), {
248       triggeringPrincipal,
249     });
250   }
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();
258         break;
259       case "Browser:OpenCaptivePortalPage":
260         this.browser.ownerGlobal.CaptivePortalWatcher.ensureCaptivePortalTab();
261         break;
262       case "Browser:PrimeMitm":
263         this.primeMitm(this.browser);
264         break;
265       case "Browser:ResetEnterpriseRootsPref":
266         Services.prefs.clearUserPref("security.enterprise_roots.enabled");
267         Services.prefs.clearUserPref("security.enterprise_roots.auto-enabled");
268         break;
269       case "Browser:ResetSSLPreferences":
270         let prefSSLImpact = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
271           return prefs.concat(Services.prefs.getChildList(root));
272         }, []);
273         for (let prefName of prefSSLImpact) {
274           Services.prefs.clearUserPref(prefName);
275         }
276         this.browser.reload();
277         break;
278       case "Browser:SSLErrorGoBack":
279         this.goBackFromErrorPage(this.browser);
280         break;
281       case "GetChangedCertPrefs":
282         let hasChangedCertPrefs = this.hasChangedCertPrefs();
283         this.sendAsyncMessage("HasChangedCertPrefs", {
284           hasChangedCertPrefs,
285         });
286         break;
287       case "ReportBlockingError":
288         this.ReportBlockingError(
289           this.browsingContext.id,
290           message.data.scheme,
291           message.data.host,
292           message.data.port,
293           message.data.path,
294           message.data.xfoAndCspInfo
295         );
296         break;
297       case "DisplayOfflineSupportPage":
298         this.displayOfflineSupportPage(message.data.supportPageSlug);
299         break;
300       case "Browser:CertExceptionError":
301         switch (message.data.elementId) {
302           case "viewCertificate": {
303             let certs = message.data.failedCertChain.map(certBase64 =>
304               encodeURIComponent(certBase64)
305             );
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, {});
313             } else {
314               window.open(url, "_blank");
315             }
316             break;
317           }
318         }
319         break;
320       case "Browser:AddTRRExcludedDomain":
321         let domain = message.data.hostname;
322         let excludedDomains = Services.prefs.getStringPref(
323           "network.trr.excluded-domains"
324         );
325         excludedDomains += `, ${domain}`;
326         Services.prefs.setStringPref(
327           "network.trr.excluded-domains",
328           excludedDomains
329         );
330         break;
331       case "OpenTRRPreferences":
332         let browser = this.browsingContext.top.embedderElement;
333         if (!browser) {
334           break;
335         }
337         let win = browser.ownerGlobal;
338         win.openPreferences("privacy-doh");
339         break;
340     }
341   }