Bug 846734 - adding certificate exception from certificate error page. r=fabrice
authorPatrick Wang <kk1fff@patrickz.net>
Tue, 05 Mar 2013 10:19:00 +0800
changeset 137213 55a8ef1cd22ff4d30544541e2604bbd827ebb2fb
parent 137212 0fa7df90f98c1dd154cb4c98f445d3d7ead5e55c
child 137214 d674eb04e687eb2e2f4d73653fd8530d72852fa1
push id336
push userakeybl@mozilla.com
push dateMon, 17 Jun 2013 22:53:19 +0000
treeherdermozilla-release@574a39cdf657 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs846734
milestone22.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 846734 - adding certificate exception from certificate error page. r=fabrice
b2g/chrome/content/ErrorPage.js
b2g/chrome/content/shell.js
b2g/chrome/jar.mn
b2g/components/ErrorPage.jsm
b2g/components/Makefile.in
new file mode 100644
--- /dev/null
+++ b/b2g/chrome/content/ErrorPage.js
@@ -0,0 +1,63 @@
+/* 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';
+
+let Cu = Components.utils;
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+
+let ErrorPageHandler = {
+  _reload: function() {
+    docShell.QueryInterface(Ci.nsIWebNavigation).reload(Ci.nsIWebNavigation.LOAD_FLAGS_NONE);
+  },
+
+  _certErrorPageEventHandler: function(e) {
+    let target = e.originalTarget;
+    let errorDoc = target.ownerDocument;
+
+    // If the event came from an ssl error page, it is one of the "Add
+    // Exception…" buttons.
+    if (/^about:certerror\?e=nssBadCert/.test(errorDoc.documentURI)) {
+      let permanent = errorDoc.getElementById("permanentExceptionButton");
+      let temp = errorDoc.getElementById("temporaryExceptionButton");
+      if (target == temp || target == permanent) {
+        sendAsyncMessage("ErrorPage:AddCertException", {
+          url: errorDoc.location.href,
+          isPermanent: target == permanent
+        });
+      }
+    }
+  },
+
+  domContentLoadedHandler: function(e) {
+    let target = e.originalTarget;
+    let targetDocShell = target.defaultView
+                               .QueryInterface(Ci.nsIInterfaceRequestor)
+                               .getInterface(Ci.nsIWebNavigation);
+    if (targetDocShell != docShell) {
+      return;
+    }
+
+    if (/^about:certerror/.test(target.documentURI)) {
+      let errorPageEventHandler = this._certErrorPageEventHandler.bind(this);
+      addEventListener("click", errorPageEventHandler, true, false);
+      let listener = function() {
+        removeEventListener("click", errorPageEventHandler, true);
+        removeEventListener("pagehide", listener, true);
+      }.bind(this);
+
+      addEventListener("pagehide", listener, true);
+    }
+  },
+
+  init: function() {
+    addMessageListener("ErrorPage:ReloadPage", this._reload.bind(this));
+    addEventListener('DOMContentLoaded',
+                     this.domContentLoadedHandler.bind(this),
+                     true);
+  }
+};
+
+ErrorPageHandler.init();
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -13,16 +13,17 @@ Cu.import('resource://gre/modules/AlarmS
 Cu.import('resource://gre/modules/ActivitiesService.jsm');
 Cu.import('resource://gre/modules/PermissionPromptHelper.jsm');
 Cu.import('resource://gre/modules/ObjectWrapper.jsm');
 Cu.import('resource://gre/modules/accessibility/AccessFu.jsm');
 Cu.import('resource://gre/modules/Payment.jsm');
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import('resource://gre/modules/UserAgentOverrides.jsm');
 Cu.import('resource://gre/modules/Keyboard.jsm');
+Cu.import('resource://gre/modules/ErrorPage.jsm');
 #ifdef MOZ_B2G_RIL
 Cu.import('resource://gre/modules/NetworkStatsService.jsm');
 #endif
 
 // identity
 Cu.import('resource://gre/modules/SignInToWebsite.jsm');
 SignInToWebsiteController.init();
 
--- a/b2g/chrome/jar.mn
+++ b/b2g/chrome/jar.mn
@@ -25,16 +25,17 @@ chrome.jar:
   content/payment.js                    (content/payment.js)
   content/identity.js                   (content/identity.js)
 
 % override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml
 % override chrome://global/skin/netError.css chrome://browser/content/netError.css
 % override chrome://global/skin/media/videocontrols.css chrome://browser/content/touchcontrols.css
 % override chrome://global/content/aboutCertError.xhtml chrome://browser/content/aboutCertError.xhtml
 
+  content/ErrorPage.js                  (content/ErrorPage.js)
   content/netError.xhtml                (content/netError.xhtml)
   content/netError.css                  (content/netError.css)
   content/aboutCertError.xhtml          (content/aboutCertError.xhtml)
   content/images/errorpage-larry-black.png (content/images/errorpage-larry-black.png)
   content/images/errorpage-larry-white.png (content/images/errorpage-larry-white.png)
   content/images/errorpage-warning.png (content/images/errorpage-warning.png)
   content/images/scrubber-hdpi.png     (content/images/scrubber-hdpi.png)
   content/images/unmute-hdpi.png       (content/images/unmute-hdpi.png)
new file mode 100644
--- /dev/null
+++ b/b2g/components/ErrorPage.jsm
@@ -0,0 +1,169 @@
+/* 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';
+
+this.EXPORTED_SYMBOLS = ['ErrorPage'];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const kErrorPageFrameScript = 'chrome://browser/content/ErrorPage.js';
+
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "CertOverrideService", function () {
+  return Cc["@mozilla.org/security/certoverride;1"]
+         .getService(Ci.nsICertOverrideService);
+});
+
+/**
+ * A class to add exceptions to override SSL certificate problems.
+ * The functionality itself is borrowed from exceptionDialog.js.
+ */
+function SSLExceptions(aCallback, aUri, aWindow) {
+  this._finishCallback = aCallback;
+  this._uri = aUri;
+  this._window = aWindow;
+};
+
+SSLExceptions.prototype = {
+  _finishCallback: null,
+  _window: null,
+  _uri: null,
+  _temporary: null,
+  _sslStatus: null,
+
+  getInterface: function SSLE_getInterface(aIID) {
+    return this.QueryInterface(aIID);
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIBadCertListener2]),
+
+  /**
+   * To collect the SSL status we intercept the certificate error here
+   * and store the status for later use.
+   */
+  notifyCertProblem: function SSLE_notifyCertProblem(aSocketInfo,
+                                                     aSslStatus,
+                                                     aTargetHost) {
+    this._sslStatus = aSslStatus.QueryInterface(Ci.nsISSLStatus);
+    Services.tm.currentThread.dispatch({
+      run: this._addOverride.bind(this)
+    }, Ci.nsIThread.DISPATCH_NORMAL);
+    return true; // suppress error UI
+  },
+
+  /**
+   * Attempt to download the certificate for the location specified to get
+   * the SSLState for the certificate and the errors.
+   */
+  _checkCert: function SSLE_checkCert() {
+    this._sslStatus = null;
+    if (!this._uri) {
+      return;
+    }
+    let req = new this._window.XMLHttpRequest();
+    try {
+      req.open("GET", this._uri.prePath, true);
+      req.channel.notificationCallbacks = this;
+      let xhrHandler = (function() {
+        req.removeEventListener("load", xhrHandler);
+        req.removeEventListener("error", xhrHandler);
+        if (!this._sslStatus) {
+          // Got response from server without an SSL error.
+          if (this._finishCallback) {
+            this._finishCallback();
+          }
+        }
+      }).bind(this);
+      req.addEventListener("load", xhrHandler);
+      req.addEventListener("error", xhrHandler);
+      req.send(null);
+    } catch (e) {
+      // We *expect* exceptions if there are problems with the certificate
+      // presented by the site.  Log it, just in case, but we can proceed here,
+      // with appropriate sanity checks
+      Components.utils.reportError("Attempted to connect to a site with a bad certificate in the add exception dialog. " +
+                                   "This results in a (mostly harmless) exception being thrown. " +
+                                   "Logged for information purposes only: " + e);
+    }
+  },
+
+  /**
+   * Internal method to create an override.
+   */
+  _addOverride: function SSLE_addOverride() {
+    let SSLStatus = this._sslStatus;
+    let uri = this._uri;
+    let flags = 0;
+
+    if (SSLStatus.isUntrusted) {
+      flags |= Ci.nsICertOverrideService.ERROR_UNTRUSTED;
+    }
+    if (SSLStatus.isDomainMismatch) {
+      flags |= Ci.nsICertOverrideService.ERROR_MISMATCH;
+    }
+    if (SSLStatus.isNotValidAtThisTime) {
+      flags |= Ci.nsICertOverrideService.ERROR_TIME;
+    }
+
+    CertOverrideService.rememberValidityOverride(
+      uri.asciiHost,
+      uri.port,
+      SSLStatus.serverCert,
+      flags,
+      this._temporary);
+
+    if (this._finishCallback) {
+      this._finishCallback();
+    }
+  },
+
+  /**
+   * Creates a permanent exception to override all overridable errors for
+   * the given URL.
+   */
+  addException: function SSLE_addException(aTemporary) {
+    this._temporary = aTemporary;
+    this._checkCert();
+  }
+};
+
+let ErrorPage = {
+  _addCertException: function(aMessage) {
+    let frameLoaderOwner = aMessage.target.QueryInterface(Ci.nsIFrameLoaderOwner);
+    let win = frameLoaderOwner.ownerDocument.defaultView;
+    let mm = frameLoaderOwner.frameLoader.messageManager;
+
+    let uri = Services.io.newURI(aMessage.data.url, null, null);
+    let sslExceptions = new SSLExceptions((function() {
+      mm.sendAsyncMessage('ErrorPage:ReloadPage');
+    }).bind(this), uri, win);
+    try {
+      sslExceptions.addException(!aMessage.data.isPermanent);
+    } catch (e) {
+      dump("Failed to set cert exception: " + e + "\n");
+    }
+  },
+
+  init: function errorPageInit() {
+    Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', false);
+    Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
+  },
+
+  observe: function errorPageObserve(aSubject, aTopic, aData) {
+    let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
+    let mm = frameLoader.messageManager;
+    try {
+      mm.loadFrameScript(kErrorPageFrameScript, true);
+    } catch (e) {
+      dump('Error loading ' + kErrorPageFrameScript + ' as frame script: ' + e + '\n');
+    }
+    mm.addMessageListener('ErrorPage:AddCertException', this._addCertException.bind(this));
+  }
+};
+
+ErrorPage.init();
--- a/b2g/components/Makefile.in
+++ b/b2g/components/Makefile.in
@@ -27,15 +27,16 @@ EXTRA_PP_COMPONENTS = \
         YoutubeProtocolHandler.js \
         RecoveryService.js \
         $(NULL)
 
 EXTRA_JS_MODULES = \
 	Keyboard.jsm \
 	TelURIParser.jsm \
 	SignInToWebsite.jsm \
+	ErrorPage.jsm \
 	$(NULL)
 
 ifdef MOZ_UPDATER
 EXTRA_PP_COMPONENTS += UpdatePrompt.js
 endif
 
 include $(topsrcdir)/config/rules.mk