mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-10-31 16:28:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			211 lines
		
	
	
	
		
			6.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
	
		
			6.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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;
 | |
|     }
 | |
| 
 | |
|     Glean.pwmgr.loginPageSafety.accumulateSingleSample(passwordSafety);
 | |
|   },
 | |
| };
 | |
| 
 | |
| XPCOMUtils.defineLazyPreferenceGetter(
 | |
|   InsecurePasswordUtils,
 | |
|   "_ignoreLocalIPAddress",
 | |
|   "security.insecure_field_warning.ignore_local_ip_address",
 | |
|   true
 | |
| );
 | 
