Bug 1553265 - allow GeckoView error pages to request a certificate exception with window.postMessage; r?snorp draft
authorThomas Wisniewski <twisniewski@mozilla.com>
Wed, 04 Dec 2019 17:49:13 -0500
changeset 2517414 3598aa0ef92ed3e8e87acd1f067e60bd976d592e
parent 2512126 53cfede1065cc3eeb57bb1f2561ac830396e84c7
child 2517415 2003100486b88442234ed070385edd2d19ba8a86
push id460519
push userwisniewskit@gmail.com
push dateThu, 05 Dec 2019 03:05:22 +0000
treeherdertry@2003100486b8 [default view] [failures only]
reviewerssnorp
bugs1553265
milestone73.0a1
Bug 1553265 - allow GeckoView error pages to request a certificate exception with window.postMessage; r?snorp
mobile/android/chrome/geckoview/GeckoViewContentChild.js
mobile/android/modules/geckoview/GeckoViewContent.jsm
--- a/mobile/android/chrome/geckoview/GeckoViewContentChild.js
+++ b/mobile/android/chrome/geckoview/GeckoViewContentChild.js
@@ -412,26 +412,78 @@ class GeckoViewContentChild extends Geck
           );
           if (manifest) {
             this.eventDispatcher.sendRequest({
               type: "GeckoView:WebAppManifest",
               manifest,
             });
           }
         });
+
+        this.maybeAddErrorPageCertOverrideListener(docShell);
         break;
       }
       case "MozFirstContentfulPaint":
         this.eventDispatcher.sendRequest({
           type: "GeckoView:FirstContentfulPaint",
         });
         break;
     }
   }
 
+  isErrorPageDocShell(docShell) {
+    // JS port of the value from nsDocShellLoadTypes.h
+    const LOAD_FLAGS_ERROR_PAGE = 0x0001;
+    const LOAD_ERROR_PAGE =
+      Ci.nsIDocShell.LOAD_CMD_NORMAL | (LOAD_FLAGS_ERROR_PAGE << 16);
+    return docShell.loadType === LOAD_ERROR_PAGE;
+  }
+
+  maybeAddErrorPageCertOverrideListener(docShell) {
+    // Only add the listener if the page is an error page. Note that we
+    // need to check the docShell's loadType, as by now the current window
+    // has changed to load our data-URL error page, so we can't just check
+    // if the URL is about:certerror/etc, nor test loadInfo.loadErrorPage.
+    if (!this.isErrorPageDocShell(docShell)) {
+      return;
+    }
+
+    // Also don't bother if there is no failed certificate to override.
+    const { failedChannel } = docShell;
+    if (!failedChannel || !failedChannel.securityInfo) {
+      return;
+    }
+
+    const serHelper = Cc[
+      "@mozilla.org/network/serialization-helper;1"
+    ].getService(Ci.nsISerializationHelper);
+    const securityInfo = serHelper.serializeToString(
+      failedChannel.securityInfo
+    );
+
+    const uri = failedChannel.QueryInterface(Ci.nsIHttpChannel).URI;
+
+    const msg = {
+      securityInfo,
+      host: uri.asciiHost,
+      port: uri.port,
+    };
+
+    addEventListener(
+      "message",
+      ({ data: { addCertException } }) => {
+        if (addCertException) {
+          msg.isTemporary = addCertException !== "permanent";
+          sendAsyncMessage("GeckoView:AddCertException", msg);
+        }
+      },
+      { capture: true }
+    );
+  }
+
   // WebProgress event handler.
   onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) {
     debug`onLocationChange`;
 
     if (this._savedState) {
       const scrolldata = this._savedState.scrolldata;
       if (scrolldata && scrolldata.zoom && scrolldata.zoom.displaySize) {
         let utils = content.windowUtils;
--- a/mobile/android/modules/geckoview/GeckoViewContent.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewContent.jsm
@@ -48,16 +48,17 @@ class GeckoViewContent extends GeckoView
     );
     this.window.addEventListener(
       "framefocusrequested",
       this,
       /* capture */ true,
       /* untrusted */ false
     );
 
+    this.messageManager.addMessageListener("GeckoView:AddCertException", this);
     this.messageManager.addMessageListener("GeckoView:DOMFullscreenExit", this);
     this.messageManager.addMessageListener(
       "GeckoView:DOMFullscreenRequest",
       this
     );
 
     Services.obs.addObserver(this, "oop-frameloader-crashed");
     Services.obs.addObserver(this, "ipc:content-shutdown");
@@ -76,16 +77,20 @@ class GeckoViewContent extends GeckoView
     );
     this.window.removeEventListener(
       "framefocusrequested",
       this,
       /* capture */ true
     );
 
     this.messageManager.removeMessageListener(
+      "GeckoView:AddCertException",
+      this
+    );
+    this.messageManager.removeMessageListener(
       "GeckoView:DOMFullscreenExit",
       this
     );
     this.messageManager.removeMessageListener(
       "GeckoView:DOMFullscreenRequest",
       this
     );
 
@@ -180,16 +185,51 @@ class GeckoViewContent extends GeckoView
     }
   }
 
   // Message manager event handler.
   receiveMessage(aMsg) {
     debug`receiveMessage: ${aMsg.name}`;
 
     switch (aMsg.name) {
+      case "GeckoView:AddCertException":
+        // based on NetErrorParent.jsm#addCertException()
+        const serHelper = Cc[
+          "@mozilla.org/network/serialization-helper;1"
+        ].getService(Ci.nsISerializationHelper);
+
+        const securityInfo = serHelper.deserializeObject(
+          aMsg.data.securityInfo
+        );
+        securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+
+        const overrideService = Cc[
+          "@mozilla.org/security/certoverride;1"
+        ].getService(Ci.nsICertOverrideService);
+        let flags = 0;
+        if (securityInfo.isUntrusted) {
+          flags |= overrideService.ERROR_UNTRUSTED;
+        }
+        if (securityInfo.isDomainMismatch) {
+          flags |= overrideService.ERROR_MISMATCH;
+        }
+        if (securityInfo.isNotValidAtThisTime) {
+          flags |= overrideService.ERROR_TIME;
+        }
+
+        overrideService.rememberValidityOverride(
+          aMsg.data.host,
+          aMsg.data.port,
+          securityInfo.serverCert,
+          flags,
+          aMsg.data.isTemporary
+        );
+
+        aMsg.target.reload();
+        break;
       case "GeckoView:DOMFullscreenExit":
         this.window.windowUtils.remoteFrameFullscreenReverted();
         break;
       case "GeckoView:DOMFullscreenRequest":
         this.window.windowUtils.remoteFrameFullscreenChanged(aMsg.target);
         break;
     }
   }