Bug 1791785 - When dumping symbols never emit INLINE_ORIGIN directives with an empty...
[gecko.git] / browser / actors / NetErrorParent.jsm
blob598225cf2b6a71c6f471739aa0ff067d040c817a
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 var EXPORTED_SYMBOLS = ["NetErrorParent"];
8 const { PrivateBrowsingUtils } = ChromeUtils.import(
9   "resource://gre/modules/PrivateBrowsingUtils.jsm"
11 const { HomePage } = ChromeUtils.import("resource:///modules/HomePage.jsm");
13 const { TelemetryController } = ChromeUtils.import(
14   "resource://gre/modules/TelemetryController.jsm"
17 const PREF_SSL_IMPACT_ROOTS = [
18   "security.tls.version.",
19   "security.ssl3.",
20   "security.tls13.",
23 const lazy = {};
25 ChromeUtils.defineModuleGetter(
26   lazy,
27   "BrowserUtils",
28   "resource://gre/modules/BrowserUtils.jsm"
31 class CaptivePortalObserver {
32   constructor(actor) {
33     this.actor = actor;
34     Services.obs.addObserver(this, "captive-portal-login-abort");
35     Services.obs.addObserver(this, "captive-portal-login-success");
36   }
38   stop() {
39     Services.obs.removeObserver(this, "captive-portal-login-abort");
40     Services.obs.removeObserver(this, "captive-portal-login-success");
41   }
43   observe(aSubject, aTopic, aData) {
44     switch (aTopic) {
45       case "captive-portal-login-abort":
46       case "captive-portal-login-success":
47         // Send a message to the content when a captive portal is freed
48         // so that error pages can refresh themselves.
49         this.actor.sendAsyncMessage("AboutNetErrorCaptivePortalFreed");
50         break;
51     }
52   }
55 class NetErrorParent extends JSWindowActorParent {
56   constructor() {
57     super();
58     this.captivePortalObserver = new CaptivePortalObserver(this);
59   }
61   didDestroy() {
62     if (this.captivePortalObserver) {
63       this.captivePortalObserver.stop();
64     }
65   }
67   get browser() {
68     return this.browsingContext.top.embedderElement;
69   }
71   hasChangedCertPrefs() {
72     let prefSSLImpact = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
73       return prefs.concat(Services.prefs.getChildList(root));
74     }, []);
75     for (let prefName of prefSSLImpact) {
76       if (Services.prefs.prefHasUserValue(prefName)) {
77         return true;
78       }
79     }
81     return false;
82   }
84   async ReportBlockingError(bcID, scheme, host, port, path, xfoAndCspInfo) {
85     // For reporting X-Frame-Options error and CSP: frame-ancestors errors, We
86     // are collecting 4 pieces of information.
87     // 1. The X-Frame-Options in the response header.
88     // 2. The CSP: frame-ancestors in the response header.
89     // 3. The URI of the frame who triggers this error.
90     // 4. The top-level URI which loads the frame.
91     //
92     // We will exclude the query strings from the reporting URIs.
93     //
94     // More details about the data we send can be found in
95     // https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/xfocsp-error-report-ping.html
96     //
98     let topBC = BrowsingContext.get(bcID).top;
99     let topURI = topBC.currentWindowGlobal.documentURI;
101     // Get the URLs without query strings.
102     let frame_uri = `${scheme}://${host}${port == -1 ? "" : ":" + port}${path}`;
103     let top_uri = `${topURI.scheme}://${topURI.hostPort}${topURI.filePath}`;
105     TelemetryController.submitExternalPing(
106       "xfocsp-error-report",
107       {
108         ...xfoAndCspInfo,
109         frame_hostname: host,
110         top_hostname: topURI.host,
111         frame_uri,
112         top_uri,
113       },
114       { addClientId: false, addEnvironment: false }
115     );
116   }
118   /**
119    * Return the default start page for the cases when the user's own homepage is
120    * infected, so we can get them somewhere safe.
121    */
122   getDefaultHomePage(win) {
123     if (PrivateBrowsingUtils.isWindowPrivate(win)) {
124       return win.BROWSER_NEW_TAB_URL;
125     }
126     let url = HomePage.getDefault();
127     // If url is a pipe-delimited set of pages, just take the first one.
128     if (url.includes("|")) {
129       url = url.split("|")[0];
130     }
131     return url;
132   }
134   /**
135    * Re-direct the browser to the previous page or a known-safe page if no
136    * previous page is found in history.  This function is used when the user
137    * browses to a secure page with certificate issues and is presented with
138    * about:certerror.  The "Go Back" button should take the user to the previous
139    * or a default start page so that even when their own homepage is on a server
140    * that has certificate errors, we can get them somewhere safe.
141    */
142   goBackFromErrorPage(browser) {
143     if (!browser.canGoBack) {
144       // If the unsafe page is the first or the only one in history, go to the
145       // start page.
146       browser.loadURI(this.getDefaultHomePage(browser.ownerGlobal), {
147         triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
148       });
149     } else {
150       browser.goBack();
151     }
152   }
154   /**
155    * This function does a canary request to a reliable, maintained endpoint, in
156    * order to help network code detect a system-wide man-in-the-middle.
157    */
158   primeMitm(browser) {
159     // If we already have a mitm canary issuer stored, then don't bother with the
160     // extra request. This will be cleared on every update ping.
161     if (Services.prefs.getStringPref("security.pki.mitm_canary_issuer", null)) {
162       return;
163     }
165     let url = Services.prefs.getStringPref(
166       "security.certerrors.mitm.priming.endpoint"
167     );
168     let request = new XMLHttpRequest({ mozAnon: true });
169     request.open("HEAD", url);
170     request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
171     request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
173     request.addEventListener("error", event => {
174       // Make sure the user is still on the cert error page.
175       if (!browser.documentURI.spec.startsWith("about:certerror")) {
176         return;
177       }
179       let secInfo = request.channel.securityInfo;
180       if (secInfo.errorCodeString != "SEC_ERROR_UNKNOWN_ISSUER") {
181         return;
182       }
184       // When we get to this point there's already something deeply wrong, it's very likely
185       // that there is indeed a system-wide MitM.
186       if (secInfo.serverCert && secInfo.serverCert.issuerName) {
187         // Grab the issuer of the certificate used in the exchange and store it so that our
188         // network-level MitM detection code has a comparison baseline.
189         Services.prefs.setStringPref(
190           "security.pki.mitm_canary_issuer",
191           secInfo.serverCert.issuerName
192         );
194         // MitM issues are sometimes caused by software not registering their root certs in the
195         // Firefox root store. We might opt for using third party roots from the system root store.
196         if (
197           Services.prefs.getBoolPref(
198             "security.certerrors.mitm.auto_enable_enterprise_roots"
199           )
200         ) {
201           if (
202             !Services.prefs.getBoolPref("security.enterprise_roots.enabled")
203           ) {
204             // Loading enterprise roots happens on a background thread, so wait for import to finish.
205             lazy.BrowserUtils.promiseObserved(
206               "psm:enterprise-certs-imported"
207             ).then(() => {
208               if (browser.documentURI.spec.startsWith("about:certerror")) {
209                 browser.reload();
210               }
211             });
213             Services.prefs.setBoolPref(
214               "security.enterprise_roots.enabled",
215               true
216             );
217             // Record that this pref was automatically set.
218             Services.prefs.setBoolPref(
219               "security.enterprise_roots.auto-enabled",
220               true
221             );
222           }
223         } else {
224           // Need to reload the page to make sure network code picks up the canary issuer pref.
225           browser.reload();
226         }
227       }
228     });
230     request.send(null);
231   }
233   displayOfflineSupportPage(supportPageSlug) {
234     const AVAILABLE_PAGES = ["connection-not-secure", "time-errors"];
235     if (!AVAILABLE_PAGES.includes(supportPageSlug)) {
236       console.log(
237         `[Not supported] Offline support is not yet available for ${supportPageSlug} errors.`
238       );
239       return;
240     }
242     let offlinePagePath = `chrome://browser/content/certerror/supportpages/${supportPageSlug}.html`;
243     let triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
244     this.browser.loadURI(offlinePagePath, { triggeringPrincipal });
245   }
247   receiveMessage(message) {
248     switch (message.name) {
249       case "Browser:EnableOnlineMode":
250         // Reset network state and refresh the page.
251         Services.io.offline = false;
252         this.browser.reload();
253         break;
254       case "Browser:OpenCaptivePortalPage":
255         this.browser.ownerGlobal.CaptivePortalWatcher.ensureCaptivePortalTab();
256         break;
257       case "Browser:PrimeMitm":
258         this.primeMitm(this.browser);
259         break;
260       case "Browser:ResetEnterpriseRootsPref":
261         Services.prefs.clearUserPref("security.enterprise_roots.enabled");
262         Services.prefs.clearUserPref("security.enterprise_roots.auto-enabled");
263         break;
264       case "Browser:ResetSSLPreferences":
265         let prefSSLImpact = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
266           return prefs.concat(Services.prefs.getChildList(root));
267         }, []);
268         for (let prefName of prefSSLImpact) {
269           Services.prefs.clearUserPref(prefName);
270         }
271         this.browser.reload();
272         break;
273       case "Browser:SSLErrorGoBack":
274         this.goBackFromErrorPage(this.browser);
275         break;
276       case "Browser:SSLErrorReportTelemetry":
277         let reportStatus = message.data.reportStatus;
278         Services.telemetry
279           .getHistogramById("TLS_ERROR_REPORT_UI")
280           .add(reportStatus);
281         break;
282       case "GetChangedCertPrefs":
283         let hasChangedCertPrefs = this.hasChangedCertPrefs();
284         this.sendAsyncMessage("HasChangedCertPrefs", {
285           hasChangedCertPrefs,
286         });
287         break;
288       case "ReportBlockingError":
289         this.ReportBlockingError(
290           this.browsingContext.id,
291           message.data.scheme,
292           message.data.host,
293           message.data.port,
294           message.data.path,
295           message.data.xfoAndCspInfo
296         );
297         break;
298       case "DisplayOfflineSupportPage":
299         this.displayOfflineSupportPage(message.data.supportPageSlug);
300         break;
301       case "Browser:CertExceptionError":
302         switch (message.data.elementId) {
303           case "viewCertificate": {
304             let window = this.browser.ownerGlobal;
305             let certs = message.data.failedCertChain.map(certBase64 =>
306               encodeURIComponent(certBase64)
307             );
308             let certsStringURL = certs.map(elem => `cert=${elem}`);
309             certsStringURL = certsStringURL.join("&");
310             let url = `about:certificate?${certsStringURL}`;
311             window.switchToTabHavingURI(url, true, {});
312             break;
313           }
314         }
315     }
316   }