Source code

Revision control

Copy as Markdown

Other Tools

/* 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 */
const STRINGS_URI = "chrome://global/locale/security/security.properties";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "log", () => {
return lazy.LoginHelper.createLogger("InsecurePasswordUtils");
});
/*
* A module that provides utility functions for form security.
*
*/
export const 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.windowGlobalChild.innerWindowId;
},
_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,
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 (HTMLFormElement.isInstance(aForm.rootElement)) {
let uri = Services.io.newURI(
aForm.rootElement.action || aForm.rootElement.baseURI
);
let principal = Services.scriptSecurityManager.createContentPrincipal(
uri,
{}
);
if (uri.schemeIs("http")) {
isFormSubmitHTTP = true;
if (
principal.isOriginPotentiallyTrustworthy ||
// 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) {
let res = aPrincipal.isLocalIpAddress;
if (res) {
lazy.log.debug(
"hasInsecureLoginForms: detected local IP address:",
aPrincipal.asciispec
);
}
return res;
},
/**s
* 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 topIsLocalIP =
aForm.ownerDocument.defaultView.windowGlobalChild.windowContext
.topWindowContext.isLocalIP;
// 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(
InsecurePasswordUtils,
"_ignoreLocalIPAddress",
"security.insecure_field_warning.ignore_local_ip_address",
true
);