Bug 762593 TOOLKIT patch. Adds logic for validating if password input fields are insecure. r=dolske
authorIvan Alagenchev <ialagenchev@mozilla.com>
Tue, 06 Aug 2013 10:14:40 -0400
changeset 149527 a46b44af697fbfa9170ccc28bb8769f085fe2fde
parent 149526 b45873908380ed181e4e31df0949d9e408250dd7
child 149528 2b03e95df6d3216fe9886ee9de62b396db888786
push idunknown
push userunknown
push dateunknown
reviewersdolske
bugs762593
milestone26.0a1
Bug 762593 TOOLKIT patch. Adds logic for validating if password input fields are insecure. r=dolske
browser/base/content/content.js
toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
toolkit/components/passwordmgr/moz.build
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -6,16 +6,18 @@
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this,
   "LoginManagerContent", "resource://gre/modules/LoginManagerContent.jsm");
+XPCOMUtils.defineLazyModuleGetter(this,
+  "InsecurePasswordUtils", "resource://gre/modules/InsecurePasswordUtils.jsm");
 
 // Bug 671101 - directly using webNavigation in this context
 // causes docshells to leak
 this.__defineGetter__("webNavigation", function () {
   return docShell.QueryInterface(Ci.nsIWebNavigation);
 });
 
 addMessageListener("WebNavigation:LoadURI", function (message) {
@@ -33,15 +35,18 @@ addMessageListener("Browser:HideSessionR
     container.hidden = true;
   }
 });
 
 if (!Services.prefs.getBoolPref("browser.tabs.remote")) {
   addEventListener("DOMContentLoaded", function(event) {
     LoginManagerContent.onContentLoaded(event);
   });
+  addEventListener("DOMFormHasPassword", function(event) {
+    InsecurePasswordUtils.checkForInsecurePasswords(event.target);
+  });
   addEventListener("DOMAutoComplete", function(event) {
     LoginManagerContent.onUsernameInput(event);
   });
   addEventListener("blur", function(event) {
     LoginManagerContent.onUsernameInput(event);
   });
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
@@ -0,0 +1,146 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = [ "InsecurePasswordUtils" ];
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cc = Components.classes;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "devtools",
+                                  "resource://gre/modules/devtools/Loader.jsm");
+
+Object.defineProperty(this, "WebConsoleUtils", {
+  get: function() {
+    return devtools.require("devtools/toolkit/webconsole/utils").Utils;
+  },
+  configurable: true,
+  enumerable: true
+});
+
+const STRINGS_URI = "chrome://global/locale/security/security.properties";
+let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
+
+this.InsecurePasswordUtils = {
+
+  _sendWebConsoleMessage : function (messageTag, domDoc) {
+    /*
+     * All web console messages are warnings for now so I decided to set the
+     * flag here and save a bit of the flag creation in the callers.
+     * It's easy to expose this later if needed
+     */
+
+    let  windowId = WebConsoleUtils.getInnerWindowId(domDoc.defaultView);
+    let category = "Insecure Password Field";
+    let flag = Ci.nsIScriptError.warningFlag;
+    let message = l10n.getStr(messageTag);
+    let consoleMsg = Cc["@mozilla.org/scripterror;1"]
+      .createInstance(Ci.nsIScriptError);
+
+    consoleMsg.initWithWindowID(
+      message, "", 0, 0, 0, flag, category, windowId);
+
+    Services.console.logMessage(consoleMsg);
+  },
+
+  /*
+   * Checks whether the passed uri is secure
+   * Check Protocol Flags to determine if scheme is secure:
+   * URI_DOES_NOT_RETURN_DATA - e.g.
+   *   "mailto"
+   * URI_IS_LOCAL_RESOURCE - e.g.
+   *   "data",
+   *   "resource",
+   *   "moz-icon"
+   * URI_INHERITS_SECURITY_CONTEXT - e.g.
+   *   "javascript"
+   * URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT - e.g.
+   *   "https",
+   *   "moz-safe-about"
+   *
+   *   The use of this logic comes directly from nsMixedContentBlocker.cpp
+   *   At the time it was decided to include these protocols since a secure
+   *   uri for mixed content blocker means that the resource can't be
+   *   easily tampered with because 1) it is sent over an encrypted channel or
+   *   2) it is a local resource that never hits the network
+   *   or 3) it is a request sent without any response that could alter
+   *   the behavior of the page. It was decided to include the same logic
+   *   here both to be consistent with MCB and to make sure we cover all
+   *   "safe" protocols. Eventually, the code here and the code in MCB
+   *   will be moved to a common location that will be referenced from
+   *   both places. Look at
+   *   https://bugzilla.mozilla.org/show_bug.cgi?id=899099 for more info.
+   */
+  _checkIfURIisSecure : function(uri) {
+    let isSafe = false;
+    let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
+    let ph = Ci.nsIProtocolHandler;
+
+    if (netutil.URIChainHasFlags(uri, ph.URI_IS_LOCAL_RESOURCE) ||
+        netutil.URIChainHasFlags(uri, ph.URI_DOES_NOT_RETURN_DATA) ||
+        netutil.URIChainHasFlags(uri, ph.URI_INHERITS_SECURITY_CONTEXT) ||
+        netutil.URIChainHasFlags(uri, ph.URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT)) {
+
+      isSafe = true;
+    }
+
+    return isSafe;
+  },
+
+  /*
+   * Checks whether the passed nested document is insecure
+   * or is inside an insecure parent document.
+   *
+   * We check the chain of frame ancestors all the way until the top document
+   * because MITM attackers could replace https:// iframes if they are nested inside
+   * http:// documents with their own content, thus creating a security risk
+   * and potentially stealing user data. Under such scenario, a user might not
+   * get a Mixed Content Blocker message, if the main document is served over HTTP
+   * and framing an HTTPS page as it would under the reverse scenario (http
+   * inside https).
+   */
+  _checkForInsecureNestedDocuments : function(domDoc) {
+    let uri = domDoc.documentURIObject;
+    if (domDoc.defaultView == domDoc.defaultView.parent) {
+      // We are at the top, nothing to check here
+      return false;
+    }
+    if (!this._checkIfURIisSecure(uri)) {
+      // We are insecure
+      return true;
+    }
+    // I am secure, but check my parent
+    return this._checkForInsecureNestedDocuments(domDoc.defaultView.parent.document);
+  },
+
+
+  /*
+   * Checks if there are insecure password fields present on the form's document
+   * i.e. passwords inside forms with http action, inside iframes with http src,
+   * or on insecure web pages. If insecure password fields are present,
+   * a log message is sent to the web console to warn developers.
+   */
+  checkForInsecurePasswords : function (aForm) {
+    var domDoc = aForm.ownerDocument;
+    let pageURI = domDoc.defaultView.top.document.documentURIObject;
+    let isSafePage = this._checkIfURIisSecure(pageURI);
+
+    if (!isSafePage) {
+      this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);
+    }
+
+    // Check if we are on an iframe with insecure src, or inside another
+    // insecure iframe or document.
+    if (this._checkForInsecureNestedDocuments(domDoc)) {
+      this._sendWebConsoleMessage("InsecurePasswordsPresentOnIframe", domDoc);
+    }
+
+    if (aForm.action.match(/^http:\/\//)) {
+      this._sendWebConsoleMessage("InsecureFormActionPasswordsPresent", domDoc);
+    }
+  },
+};
--- a/toolkit/components/passwordmgr/moz.build
+++ b/toolkit/components/passwordmgr/moz.build
@@ -27,10 +27,11 @@ EXTRA_COMPONENTS += [
     'storage-Legacy.js',
 ]
 
 EXTRA_PP_COMPONENTS += [
     'storage-mozStorage.js',
 ]
 
 EXTRA_JS_MODULES += [
+    'InsecurePasswordUtils.jsm',
     'LoginManagerContent.jsm',
 ]