forked from mirrors/gecko-dev
		
	Differential Revision: https://phabricator.services.mozilla.com/D36542 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			241 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			241 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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 EXPORTED_SYMBOLS = ["AboutNetErrorHandler"];
 | 
						|
 | 
						|
const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."];
 | 
						|
const { RemotePages } = ChromeUtils.import(
 | 
						|
  "resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm"
 | 
						|
);
 | 
						|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 | 
						|
const { PrivateBrowsingUtils } = ChromeUtils.import(
 | 
						|
  "resource://gre/modules/PrivateBrowsingUtils.jsm"
 | 
						|
);
 | 
						|
const { SessionStore } = ChromeUtils.import(
 | 
						|
  "resource:///modules/sessionstore/SessionStore.jsm"
 | 
						|
);
 | 
						|
const { HomePage } = ChromeUtils.import("resource:///modules/HomePage.jsm");
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  this,
 | 
						|
  "BrowserUtils",
 | 
						|
  "resource://gre/modules/BrowserUtils.jsm"
 | 
						|
);
 | 
						|
 | 
						|
var AboutNetErrorHandler = {
 | 
						|
  _inited: false,
 | 
						|
  _topics: [
 | 
						|
    "Browser:EnableOnlineMode",
 | 
						|
    "Browser:OpenCaptivePortalPage",
 | 
						|
    "Browser:PrimeMitm",
 | 
						|
    "Browser:ResetEnterpriseRootsPref",
 | 
						|
    "Browser:SSLErrorGoBack",
 | 
						|
    "GetChangedCertPrefs",
 | 
						|
  ],
 | 
						|
 | 
						|
  init() {
 | 
						|
    this._boundReceiveMessage = this.receiveMessage.bind(this);
 | 
						|
    this.pageListener = new RemotePages(["about:certerror", "about:neterror"]);
 | 
						|
    for (let topic of this._topics) {
 | 
						|
      this.pageListener.addMessageListener(topic, this._boundReceiveMessage);
 | 
						|
    }
 | 
						|
    this._inited = true;
 | 
						|
 | 
						|
    Services.obs.addObserver(this, "captive-portal-login-abort");
 | 
						|
    Services.obs.addObserver(this, "captive-portal-login-success");
 | 
						|
  },
 | 
						|
 | 
						|
  uninit() {
 | 
						|
    if (!this._inited) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    for (let topic of this._topics) {
 | 
						|
      this.pageListener.removeMessageListener(topic, this._boundReceiveMessage);
 | 
						|
    }
 | 
						|
    this.pageListener.destroy();
 | 
						|
 | 
						|
    Services.obs.removeObserver(this, "captive-portal-login-abort");
 | 
						|
    Services.obs.removeObserver(this, "captive-portal-login-success");
 | 
						|
  },
 | 
						|
 | 
						|
  observe(aSubject, aTopic, aData) {
 | 
						|
    switch (aTopic) {
 | 
						|
      case "captive-portal-login-abort":
 | 
						|
      case "captive-portal-login-success":
 | 
						|
        // Send a message to the content when a captive portal is freed
 | 
						|
        // so that error pages can refresh themselves.
 | 
						|
        this.pageListener.sendAsyncMessage("AboutNetErrorCaptivePortalFreed");
 | 
						|
        break;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  receiveMessage(msg) {
 | 
						|
    switch (msg.name) {
 | 
						|
      case "Browser:EnableOnlineMode":
 | 
						|
        // Reset network state and refresh the page.
 | 
						|
        Services.io.offline = false;
 | 
						|
        msg.target.browser.reload();
 | 
						|
        break;
 | 
						|
      case "Browser:OpenCaptivePortalPage":
 | 
						|
        Services.obs.notifyObservers(null, "ensure-captive-portal-tab");
 | 
						|
        break;
 | 
						|
      case "Browser:PrimeMitm":
 | 
						|
        this.primeMitm(msg.target.browser);
 | 
						|
        break;
 | 
						|
      case "Browser:ResetEnterpriseRootsPref":
 | 
						|
        Services.prefs.clearUserPref("security.enterprise_roots.enabled");
 | 
						|
        Services.prefs.clearUserPref("security.enterprise_roots.auto-enabled");
 | 
						|
        break;
 | 
						|
      case "Browser:SSLErrorGoBack":
 | 
						|
        this.goBackFromErrorPage(msg.target.browser.ownerGlobal);
 | 
						|
        break;
 | 
						|
      case "Browser:SSLErrorReportTelemetry":
 | 
						|
        let reportStatus = msg.data.reportStatus;
 | 
						|
        Services.telemetry
 | 
						|
          .getHistogramById("TLS_ERROR_REPORT_UI")
 | 
						|
          .add(reportStatus);
 | 
						|
        break;
 | 
						|
      case "GetChangedCertPrefs":
 | 
						|
        let hasChangedCertPrefs = this.hasChangedCertPrefs();
 | 
						|
        this.pageListener.sendAsyncMessage("HasChangedCertPrefs", {
 | 
						|
          hasChangedCertPrefs,
 | 
						|
        });
 | 
						|
        break;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  hasChangedCertPrefs() {
 | 
						|
    let prefSSLImpact = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
 | 
						|
      return prefs.concat(Services.prefs.getChildList(root));
 | 
						|
    }, []);
 | 
						|
    for (let prefName of prefSSLImpact) {
 | 
						|
      if (Services.prefs.prefHasUserValue(prefName)) {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return false;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Re-direct the browser to the previous page or a known-safe page if no
 | 
						|
   * previous page is found in history.  This function is used when the user
 | 
						|
   * browses to a secure page with certificate issues and is presented with
 | 
						|
   * about:certerror.  The "Go Back" button should take the user to the previous
 | 
						|
   * or a default start page so that even when their own homepage is on a server
 | 
						|
   * that has certificate errors, we can get them somewhere safe.
 | 
						|
   */
 | 
						|
  goBackFromErrorPage(win) {
 | 
						|
    let state = JSON.parse(SessionStore.getTabState(win.gBrowser.selectedTab));
 | 
						|
    if (state.index == 1) {
 | 
						|
      // If the unsafe page is the first or the only one in history, go to the
 | 
						|
      // start page.
 | 
						|
      win.gBrowser.loadURI(this.getDefaultHomePage(win), {
 | 
						|
        triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
 | 
						|
      });
 | 
						|
    } else {
 | 
						|
      win.gBrowser.goBack();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Return the default start page for the cases when the user's own homepage is
 | 
						|
   * infected, so we can get them somewhere safe.
 | 
						|
   */
 | 
						|
  getDefaultHomePage(win) {
 | 
						|
    let url = win.BROWSER_NEW_TAB_URL;
 | 
						|
    if (PrivateBrowsingUtils.isWindowPrivate(win)) {
 | 
						|
      return url;
 | 
						|
    }
 | 
						|
    url = HomePage.getDefault();
 | 
						|
    // If url is a pipe-delimited set of pages, just take the first one.
 | 
						|
    if (url.includes("|")) {
 | 
						|
      url = url.split("|")[0];
 | 
						|
    }
 | 
						|
    return url;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * This function does a canary request to a reliable, maintained endpoint, in
 | 
						|
   * order to help network code detect a system-wide man-in-the-middle.
 | 
						|
   */
 | 
						|
  primeMitm(browser) {
 | 
						|
    // If we already have a mitm canary issuer stored, then don't bother with the
 | 
						|
    // extra request. This will be cleared on every update ping.
 | 
						|
    if (Services.prefs.getStringPref("security.pki.mitm_canary_issuer", null)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let url = Services.prefs.getStringPref(
 | 
						|
      "security.certerrors.mitm.priming.endpoint"
 | 
						|
    );
 | 
						|
    let request = new XMLHttpRequest({ mozAnon: true });
 | 
						|
    request.open("HEAD", url);
 | 
						|
    request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
 | 
						|
    request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
 | 
						|
 | 
						|
    request.addEventListener("error", event => {
 | 
						|
      // Make sure the user is still on the cert error page.
 | 
						|
      if (!browser.documentURI.spec.startsWith("about:certerror")) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      let secInfo = request.channel.securityInfo.QueryInterface(
 | 
						|
        Ci.nsITransportSecurityInfo
 | 
						|
      );
 | 
						|
      if (secInfo.errorCodeString != "SEC_ERROR_UNKNOWN_ISSUER") {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      // When we get to this point there's already something deeply wrong, it's very likely
 | 
						|
      // that there is indeed a system-wide MitM.
 | 
						|
      if (secInfo.serverCert && secInfo.serverCert.issuerName) {
 | 
						|
        // Grab the issuer of the certificate used in the exchange and store it so that our
 | 
						|
        // network-level MitM detection code has a comparison baseline.
 | 
						|
        Services.prefs.setStringPref(
 | 
						|
          "security.pki.mitm_canary_issuer",
 | 
						|
          secInfo.serverCert.issuerName
 | 
						|
        );
 | 
						|
 | 
						|
        // MitM issues are sometimes caused by software not registering their root certs in the
 | 
						|
        // Firefox root store. We might opt for using third party roots from the system root store.
 | 
						|
        if (
 | 
						|
          Services.prefs.getBoolPref(
 | 
						|
            "security.certerrors.mitm.auto_enable_enterprise_roots"
 | 
						|
          )
 | 
						|
        ) {
 | 
						|
          if (
 | 
						|
            !Services.prefs.getBoolPref("security.enterprise_roots.enabled")
 | 
						|
          ) {
 | 
						|
            // Loading enterprise roots happens on a background thread, so wait for import to finish.
 | 
						|
            BrowserUtils.promiseObserved("psm:enterprise-certs-imported").then(
 | 
						|
              () => {
 | 
						|
                if (browser.documentURI.spec.startsWith("about:certerror")) {
 | 
						|
                  browser.reload();
 | 
						|
                }
 | 
						|
              }
 | 
						|
            );
 | 
						|
 | 
						|
            Services.prefs.setBoolPref(
 | 
						|
              "security.enterprise_roots.enabled",
 | 
						|
              true
 | 
						|
            );
 | 
						|
            // Record that this pref was automatically set.
 | 
						|
            Services.prefs.setBoolPref(
 | 
						|
              "security.enterprise_roots.auto-enabled",
 | 
						|
              true
 | 
						|
            );
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          // Need to reload the page to make sure network code picks up the canary issuer pref.
 | 
						|
          browser.reload();
 | 
						|
        }
 | 
						|
      }
 | 
						|
    });
 | 
						|
 | 
						|
    request.send(null);
 | 
						|
  },
 | 
						|
};
 |