Bug 1226928 - dochsell changes for content-signing on remote about:newtab, r=bz,mconley
authorFranziskus Kiefer <franziskuskiefer@gmail.com>
Mon, 14 Mar 2016 11:57:03 +0100
changeset 288556 7829b99f0d59d6c5fce347e3a43336f9a4a94710
parent 288555 99d90f0790854bf63853c34fed3141c5cf5cffae
child 288557 4c29953376d9d549820a49ef68c02571f56d1dee
push id30084
push userkwierso@gmail.com
push dateTue, 15 Mar 2016 00:39:07 +0000
treeherdermozilla-central@422077f61bcb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, mconley
bugs1226928
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1226928 - dochsell changes for content-signing on remote about:newtab, r=bz,mconley
browser/components/about/AboutRedirector.cpp
browser/components/newtab/aboutNewTabService.js
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -150,24 +150,36 @@ AboutRedirector::NewChannel(nsIURI* aURI
 
   nsresult rv;
   nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   for (int i = 0; i < kRedirTotal; i++) {
     if (!strcmp(path.get(), kRedirMap[i].id)) {
       nsAutoCString url;
+      nsLoadFlags loadFlags = static_cast<nsLoadFlags>(nsIChannel::LOAD_NORMAL);
 
       if (path.EqualsLiteral("newtab")) {
         // let the aboutNewTabService decide where to redirect
         nsCOMPtr<nsIAboutNewTabService> aboutNewTabService =
           do_GetService("@mozilla.org/browser/aboutnewtab-service;1", &rv);
         NS_ENSURE_SUCCESS(rv, rv);
         rv = aboutNewTabService->GetDefaultURL(url);
         NS_ENSURE_SUCCESS(rv, rv);
+
+        // if about:newtab points to an external resource we have to make sure
+        // the content is signed and trusted
+        bool remoteEnabled = false;
+        rv = aboutNewTabService->GetRemoteEnabled(&remoteEnabled);
+        NS_ENSURE_SUCCESS(rv, rv);
+        if (remoteEnabled) {
+          NS_ENSURE_ARG_POINTER(aLoadInfo);
+          aLoadInfo->SetVerifySignedContent(true);
+          loadFlags = static_cast<nsLoadFlags>(nsIChannel::LOAD_REPLACE);
+        }
       }
       // fall back to the specified url in the map
       if (url.IsEmpty()) {
         url.AssignASCII(kRedirMap[i].url);
       }
 
       nsCOMPtr<nsIChannel> tempChannel;
       nsCOMPtr<nsIURI> tempURI;
@@ -178,19 +190,19 @@ AboutRedirector::NewChannel(nsIURI* aURI
       // chrome:// or resource://) then set the LOAD_REPLACE flag on the
       // channel which forces the channel owner to reflect the displayed
       // URL rather then being the systemPrincipal.
       bool isUIResource = false;
       rv = NS_URIChainHasFlags(tempURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
                                &isUIResource);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      nsLoadFlags loadFlags =
-        isUIResource ? static_cast<nsLoadFlags>(nsIChannel::LOAD_NORMAL)
-                     : static_cast<nsLoadFlags>(nsIChannel::LOAD_REPLACE);
+      loadFlags = isUIResource
+                    ? static_cast<nsLoadFlags>(nsIChannel::LOAD_NORMAL)
+                    : static_cast<nsLoadFlags>(nsIChannel::LOAD_REPLACE);
 
       rv = NS_NewChannelInternal(getter_AddRefs(tempChannel),
                                  tempURI,
                                  aLoadInfo,
                                  nullptr, // aLoadGroup
                                  nullptr, // aCallbacks
                                  loadFlags);
       NS_ENSURE_SUCCESS(rv, rv);
--- a/browser/components/newtab/aboutNewTabService.js
+++ b/browser/components/newtab/aboutNewTabService.js
@@ -27,16 +27,19 @@ const LOCAL_NEWTAB_URL = "chrome://brows
 
 const REMOTE_NEWTAB_PATH = "/v%VERSION%/%CHANNEL%/%LOCALE%/index.html";
 
 const ABOUT_URL = "about:newtab";
 
 // Pref that tells if remote newtab is enabled
 const PREF_REMOTE_ENABLED = "browser.newtabpage.remote";
 
+// Pref branch necesssary for testing
+const PREF_REMOTE_CS_TEST = "browser.newtabpage.remote.content-signing-test";
+
 // The preference that tells whether to match the OS locale
 const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
 
 // The preference that tells what locale the user selected
 const PREF_SELECTED_LOCALE = "general.useragent.locale";
 
 // The preference that tells what remote mode is enabled.
 const PREF_REMOTE_MODE = "browser.newtabpage.remote.mode";
@@ -121,35 +124,42 @@ AboutNewTabService.prototype = {
    */
   toggleRemote(stateEnabled, forceState) {
 
     if (!forceState && (this._overriden || stateEnabled === this._remoteEnabled)) {
       // exit there is no change of state
       return false;
     }
 
+    let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST);
     if (stateEnabled) {
-      this._remoteURL = this.generateRemoteURL();
+      if (!csTest) {
+        this._remoteURL = this.generateRemoteURL();
+      } else {
+        this._remoteURL = this._newTabURL;
+      }
       NewTabPrefsProvider.prefs.on(
         PREF_SELECTED_LOCALE,
         this._updateRemoteMaybe);
       NewTabPrefsProvider.prefs.on(
         PREF_MATCH_OS_LOCALE,
         this._updateRemoteMaybe);
       NewTabPrefsProvider.prefs.on(
         PREF_REMOTE_MODE,
         this._updateRemoteMaybe);
       this._remoteEnabled = true;
     } else {
       NewTabPrefsProvider.prefs.off(PREF_SELECTED_LOCALE, this._updateRemoteMaybe);
       NewTabPrefsProvider.prefs.off(PREF_MATCH_OS_LOCALE, this._updateRemoteMaybe);
       NewTabPrefsProvider.prefs.off(PREF_REMOTE_MODE, this._updateRemoteMaybe);
       this._remoteEnabled = false;
     }
-    this._newTabURL = ABOUT_URL;
+    if (!csTest) {
+      this._newTabURL = ABOUT_URL;
+    }
     return true;
   },
 
   /*
    * Generate a default url based on remote mode, version, locale and update channel
    */
   generateRemoteURL() {
     let releaseName = this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
@@ -165,20 +175,23 @@ AboutNewTabService.prototype = {
   },
 
   /*
    * Returns the default URL.
    *
    * This URL only depends on the browser.newtabpage.remote pref. Overriding
    * the newtab page has no effect on the result of this function.
    *
+   * The result is also the remote URL if this is in a test (PREF_REMOTE_CS_TEST)
+   *
    * @returns {String} the default newtab URL, remote or local depending on browser.newtabpage.remote
    */
   get defaultURL() {
-    if (this._remoteEnabled) {
+    let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST);
+    if (this._remoteEnabled || csTest)  {
       return this._remoteURL;
     }
     return LOCAL_NEWTAB_URL;
   },
 
   /*
    * Updates the remote location when the page is not overriden.
    *
@@ -214,38 +227,44 @@ AboutNewTabService.prototype = {
     return REMOTE_NEWTAB_VERSION;
   },
 
   get remoteReleaseName() {
     return this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
   },
 
   set newTabURL(aNewTabURL) {
+    let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST);
     aNewTabURL = aNewTabURL.trim();
     if (aNewTabURL === ABOUT_URL) {
       // avoid infinite redirects in case one sets the URL to about:newtab
       this.resetNewTabURL();
       return;
     } else if (aNewTabURL === "") {
       aNewTabURL = "about:blank";
     }
     let remoteURL = this.generateRemoteURL();
     let prefRemoteEnabled = Services.prefs.getBoolPref(PREF_REMOTE_ENABLED);
     let isResetLocal = !prefRemoteEnabled && aNewTabURL === LOCAL_NEWTAB_URL;
     let isResetRemote = prefRemoteEnabled && aNewTabURL === remoteURL;
 
     if (isResetLocal || isResetRemote) {
-      if (this._overriden) {
-        // only trigger a reset if previously overridden
+      if (this._overriden && !csTest) {
+        // only trigger a reset if previously overridden and this is no test
         this.resetNewTabURL();
       }
       return;
     }
     // turn off remote state if needed
-    this.toggleRemote(false);
+    if (!csTest) {
+      this.toggleRemote(false);
+    } else {
+      // if this is a test, we want the remoteURL to be set
+      this._remoteURL = aNewTabURL;
+    }
     this._newTabURL = aNewTabURL;
     this._overridden = true;
     Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
   },
 
   get overridden() {
     return this._overridden;
   },
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -7574,16 +7574,27 @@ nsDocShell::EndPageLoad(nsIWebProgress* 
   //   4. Throw an error dialog box...
   //
   if (url && NS_FAILED(aStatus)) {
     if (aStatus == NS_ERROR_FILE_NOT_FOUND ||
         aStatus == NS_ERROR_CORRUPTED_CONTENT ||
         aStatus == NS_ERROR_INVALID_CONTENT_ENCODING) {
       DisplayLoadError(aStatus, url, nullptr, aChannel);
       return NS_OK;
+    } else if (aStatus == NS_ERROR_INVALID_SIGNATURE) {
+      // NS_ERROR_INVALID_SIGNATURE indicates a content-signature error.
+      // This currently only happens in case a remote about page fails.
+      // We have to load a fallback in this case.
+      // XXX: We always load about blank here, firefox has to overwrite this if
+      // it wants to display something else.
+      return LoadURI(MOZ_UTF16("about:blank"),  // URI string
+                     nsIChannel::LOAD_NORMAL,   // Load flags
+                     nullptr,                   // Referring URI
+                     nullptr,                   // Post data stream
+                     nullptr);                  // Headers stream
     }
 
     // Handle iframe document not loading error because source was
     // a tracking URL. We make a note of this iframe node by including
     // it in a dedicated array of blocked tracking nodes under its parent
     // document. (document of parent window of blocked document)
     if (isTopFrame == false && aStatus == NS_ERROR_TRACKING_URI) {
       // frameElement is our nsIContent to be annotated
@@ -9544,16 +9555,37 @@ nsDocShell::CreatePrincipalFromReferrer(
   attrs.InheritFromDocShellToDoc(mOriginAttributes, aReferrer);
   nsCOMPtr<nsIPrincipal> prin =
     BasePrincipal::CreateCodebasePrincipal(aReferrer, attrs);
   prin.forget(aResult);
 
   return *aResult ? NS_OK : NS_ERROR_FAILURE;
 }
 
+bool
+nsDocShell::IsAboutNewtab(nsIURI* aURI)
+{
+  if (!aURI) {
+    return false;
+  }
+  bool isAbout;
+  if (NS_WARN_IF(NS_FAILED(aURI->SchemeIs("about", &isAbout)))) {
+    return false;
+  }
+  if (!isAbout) {
+    return false;
+  }
+
+  nsAutoCString module;
+  if (NS_WARN_IF(NS_FAILED(NS_GetAboutModuleName(aURI, module)))) {
+    return false;
+  }
+  return module.Equals("newtab");
+}
+
 NS_IMETHODIMP
 nsDocShell::InternalLoad(nsIURI* aURI,
                          nsIURI* aOriginalURI,
                          bool aLoadReplace,
                          nsIURI* aReferrer,
                          uint32_t aReferrerPolicy,
                          nsISupports* aOwner,
                          uint32_t aFlags,
@@ -10213,18 +10245,26 @@ nsDocShell::InternalLoad(nsIURI* aURI,
       return NS_OK;
     }
   }
 
   // Check if the webbrowser chrome wants the load to proceed; this can be
   // used to cancel attempts to load URIs in the wrong process.
   nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner);
   if (browserChrome3) {
+    // In case this is a remote newtab load, set aURI to aOriginalURI (newtab).
+    // This ensures that the verifySignedContent flag is set on loadInfo in
+    // DoURILoad.
+    nsIURI* uriForShouldLoadCheck = aURI;
+    if (IsAboutNewtab(aOriginalURI)) {
+      uriForShouldLoadCheck = aOriginalURI;
+    }
     bool shouldLoad;
-    rv = browserChrome3->ShouldLoadURI(this, aURI, aReferrer, &shouldLoad);
+    rv = browserChrome3->ShouldLoadURI(this, uriForShouldLoadCheck, aReferrer,
+                                       &shouldLoad);
     if (NS_SUCCEEDED(rv) && !shouldLoad) {
       return NS_OK;
     }
   }
 
   // mContentViewer->PermitUnload can destroy |this| docShell, which
   // causes the next call of CanSavePresentation to crash.
   // Hold onto |this| until we return, to prevent a crash from happening.
@@ -10864,16 +10904,25 @@ nsDocShell::DoURILoad(nsIURI* aURI,
     if (aHeadersData) {
       rv = AddHeadersToChannel(aHeadersData, httpChannel);
     }
     // Set the referrer explicitly
     if (aReferrerURI && aSendReferrer) {
       // Referrer is currenly only set for link clicks here.
       httpChannel->SetReferrerWithPolicy(aReferrerURI, aReferrerPolicy);
     }
+    // set Content-Signature enforcing bit if aOriginalURI == about:newtab
+    if (aOriginalURI && httpChannel) {
+      if (IsAboutNewtab(aOriginalURI)) {
+        nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->GetLoadInfo();
+        if (loadInfo) {
+          loadInfo->SetVerifySignedContent(true);
+        }
+      }
+    }
   }
 
   nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(channel);
   if (scriptChannel) {
     // Allow execution against our context if the principals match
     scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
   }
 
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -723,16 +723,19 @@ protected:
   already_AddRefed<nsDocShell> GetParentDocshell();
 
   // Check if we have an app redirect registered for the URI and redirect if
   // needed. Returns true if a redirect happened, false otherwise.
   bool DoAppRedirectIfNeeded(nsIURI* aURI,
                              nsIDocShellLoadInfo* aLoadInfo,
                              bool aFirstParty);
 
+  // Check if aURI is about:newtab.
+  bool IsAboutNewtab(nsIURI* aURI);
+
 protected:
   nsresult GetCurScrollPos(int32_t aScrollOrientation, int32_t* aCurPos);
   nsresult SetCurScrollPosEx(int32_t aCurHorizontalPos,
                              int32_t aCurVerticalPos);
 
   // Override the parent setter from nsDocLoader
   virtual nsresult SetDocLoaderParent(nsDocLoader* aLoader) override;