DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (409f3966645a)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
/* 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/. */

/* ownerGlobal doesn't exist in content privileged windows. */
/* eslint-disable mozilla/use-ownerGlobal */

var EXPORTED_SYMBOLS = [ "InsecurePasswordUtils" ];

const STRINGS_URI = "chrome://global/locale/security/security.properties";

ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager",
                                   "@mozilla.org/contentsecuritymanager;1",
                                   "nsIContentSecurityManager");
XPCOMUtils.defineLazyServiceGetter(this, "gScriptSecurityManager",
                                   "@mozilla.org/scriptsecuritymanager;1",
                                   "nsIScriptSecurityManager");
ChromeUtils.defineModuleGetter(this, "LoginHelper",
                               "resource://gre/modules/LoginHelper.jsm");

XPCOMUtils.defineLazyGetter(this, "log", () => {
  return LoginHelper.createLogger("InsecurePasswordUtils");
});

/*
 * A module that provides utility functions for form security.
 *
 */
var InsecurePasswordUtils = {
  _formRootsWarned: new WeakMap(),

  /**
   * Gets the ID of the inner window of this DOM window.
   *
   * @param nsIDOMWindow window
   * @return integer
   *         Inner ID for the given window.
   */
  _getInnerWindowId(window) {
    return window.windowUtils.currentInnerWindowID;
  },

  _sendWebConsoleMessage(messageTag, domDoc) {
    let windowId = this._getInnerWindowId(domDoc.defaultView);
    let category = "Insecure Password Field";
    // All web console messages are warnings for now.
    let flag = Ci.nsIScriptError.warningFlag;
    let bundle = Services.strings.createBundle(STRINGS_URI);
    let message = bundle.GetStringFromName(messageTag);
    let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
    consoleMsg.initWithWindowID(message, domDoc.location.href, 0, 0, 0, flag, category, windowId);

    Services.console.logMessage(consoleMsg);
  },

  /**
   * Gets the security state of the passed form.
   *
   * @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
   *
   * @returns {Object} An object with the following boolean values:
   *  isFormSubmitHTTP: if the submit action is an http:// URL
   *  isFormSubmitSecure: if the submit action URL is secure,
   *    either because it is HTTPS or because its origin is considered trustworthy
   */
  _checkFormSecurity(aForm) {
    let isFormSubmitHTTP = false, isFormSubmitSecure = false;
    if (ChromeUtils.getClassName(aForm.rootElement) === "HTMLFormElement") {
      let uri = Services.io.newURI(aForm.rootElement.action || aForm.rootElement.baseURI);
      let principal = gScriptSecurityManager.createCodebasePrincipal(uri, {});

      if (uri.schemeIs("http")) {
        isFormSubmitHTTP = true;
        if (gContentSecurityManager.isOriginPotentiallyTrustworthy(principal) ||
            // Ignore sites with local IP addresses pointing to local forms.
            (this._isPrincipalForLocalIPAddress(aForm.rootElement.nodePrincipal) &&
             this._isPrincipalForLocalIPAddress(principal))) {
          isFormSubmitSecure = true;
        }
      } else {
        isFormSubmitSecure = true;
      }
    }

    return { isFormSubmitHTTP, isFormSubmitSecure };
  },

  _isPrincipalForLocalIPAddress(aPrincipal) {
    try {
      let uri = aPrincipal.URI;
      if (Services.io.hostnameIsLocalIPAddress(uri)) {
        log.debug("hasInsecureLoginForms: detected local IP address:", uri);
        return true;
      }
    } catch (e) {
      log.debug("hasInsecureLoginForms: unable to check for local IP address:", e);
    }
    return false;
  },

  /**
   * 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.
   *
   * @param {FormLike} aForm A form-like object. @See {LoginFormFactory}
   * @return {boolean} whether the form is secure
   */
  isFormSecure(aForm) {
    let isSafePage = aForm.ownerDocument.defaultView.isSecureContext;

    // Ignore insecure documents with URLs that are local IP addresses.
    // This is done because the vast majority of routers and other devices
    // on the network do not use HTTPS, making this warning show up almost
    // constantly on local connections, which annoys users and hurts our cause.
    if (!isSafePage && this._ignoreLocalIPAddress) {
      let isLocalIP = this._isPrincipalForLocalIPAddress(aForm.rootElement.nodePrincipal);
      let topWindow = aForm.ownerDocument.defaultView.top;
      let topIsLocalIP = this._isPrincipalForLocalIPAddress(topWindow.document.nodePrincipal);

      // Only consider the page safe if the top window has a local IP address
      // and, if this is an iframe, the iframe also has a local IP address.
      if (isLocalIP && topIsLocalIP) {
        isSafePage = true;
      }
    }

    let { isFormSubmitSecure, isFormSubmitHTTP } = this._checkFormSecurity(aForm);

    return isSafePage && (isFormSubmitSecure || !isFormSubmitHTTP);
  },

  /**
   * Report insecure password fields in a form to the web console to warn developers.
   *
   * @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
   */
  reportInsecurePasswords(aForm) {
    if (this._formRootsWarned.has(aForm.rootElement) ||
        this._formRootsWarned.get(aForm.rootElement)) {
      return;
    }

    let domDoc = aForm.ownerDocument;
    let isSafePage = domDoc.defaultView.isSecureContext;

    let { isFormSubmitHTTP, isFormSubmitSecure } = this._checkFormSecurity(aForm);

    if (!isSafePage) {
      if (domDoc.defaultView == domDoc.defaultView.parent) {
        this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);
      } else {
        this._sendWebConsoleMessage("InsecurePasswordsPresentOnIframe", domDoc);
      }
      this._formRootsWarned.set(aForm.rootElement, true);
    } else if (isFormSubmitHTTP && !isFormSubmitSecure) {
      this._sendWebConsoleMessage("InsecureFormActionPasswordsPresent", domDoc);
      this._formRootsWarned.set(aForm.rootElement, true);
    }

    // The safety of a password field determined by the form action and the page protocol
    let passwordSafety;
    if (isSafePage) {
      if (isFormSubmitSecure) {
        passwordSafety = 0;
      } else if (isFormSubmitHTTP) {
        passwordSafety = 1;
      } else {
        passwordSafety = 2;
      }
    } else if (isFormSubmitSecure) {
      passwordSafety = 3;
    } else if (isFormSubmitHTTP) {
      passwordSafety = 4;
    } else {
      passwordSafety = 5;
    }

    Services.telemetry.getHistogramById("PWMGR_LOGIN_PAGE_SAFETY").add(passwordSafety);
  },
};

XPCOMUtils.defineLazyPreferenceGetter(this.InsecurePasswordUtils, "_ignoreLocalIPAddress",
                                      "security.insecure_field_warning.ignore_local_ip_address", true);