InsecurePasswordUtils.jsm 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. this.EXPORTED_SYMBOLS = [ "InsecurePasswordUtils" ];
  5. const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
  6. const STRINGS_URI = "chrome://global/locale/security/security.properties";
  7. Cu.import("resource://gre/modules/Services.jsm");
  8. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  9. XPCOMUtils.defineLazyModuleGetter(this, "devtools",
  10. "resource://devtools/shared/Loader.jsm");
  11. XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager",
  12. "@mozilla.org/contentsecuritymanager;1",
  13. "nsIContentSecurityManager");
  14. XPCOMUtils.defineLazyServiceGetter(this, "gScriptSecurityManager",
  15. "@mozilla.org/scriptsecuritymanager;1",
  16. "nsIScriptSecurityManager");
  17. XPCOMUtils.defineLazyGetter(this, "WebConsoleUtils", () => {
  18. return this.devtools.require("devtools/server/actors/utils/webconsole-utils").Utils;
  19. });
  20. /*
  21. * A module that provides utility functions for form security.
  22. *
  23. * Note:
  24. * This module uses isSecureContextIfOpenerIgnored instead of isSecureContext.
  25. *
  26. * We don't want to expose JavaScript APIs in a non-Secure Context even if
  27. * the context is only insecure because the windows has an insecure opener.
  28. * Doing so prevents sites from implementing postMessage workarounds to enable
  29. * an insecure opener to gain access to Secure Context-only APIs. However,
  30. * in the case of form fields such as password fields we don't need to worry
  31. * about whether the opener is secure or not. In fact to flag a password
  32. * field as insecure in such circumstances would unnecessarily confuse our
  33. * users.
  34. */
  35. this.InsecurePasswordUtils = {
  36. _formRootsWarned: new WeakMap(),
  37. _sendWebConsoleMessage(messageTag, domDoc) {
  38. let windowId = WebConsoleUtils.getInnerWindowId(domDoc.defaultView);
  39. let category = "Insecure Password Field";
  40. // All web console messages are warnings for now.
  41. let flag = Ci.nsIScriptError.warningFlag;
  42. let bundle = Services.strings.createBundle(STRINGS_URI);
  43. let message = bundle.GetStringFromName(messageTag);
  44. let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
  45. consoleMsg.initWithWindowID(message, domDoc.location.href, 0, 0, 0, flag, category, windowId);
  46. Services.console.logMessage(consoleMsg);
  47. },
  48. /**
  49. * Gets the security state of the passed form.
  50. *
  51. * @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
  52. *
  53. * @returns {Object} An object with the following boolean values:
  54. * isFormSubmitHTTP: if the submit action is an http:// URL
  55. * isFormSubmitSecure: if the submit action URL is secure,
  56. * either because it is HTTPS or because its origin is considered trustworthy
  57. */
  58. _checkFormSecurity(aForm) {
  59. let isFormSubmitHTTP = false, isFormSubmitSecure = false;
  60. if (aForm.rootElement instanceof Ci.nsIDOMHTMLFormElement) {
  61. let uri = Services.io.newURI(aForm.rootElement.action || aForm.rootElement.baseURI,
  62. null, null);
  63. let principal = gScriptSecurityManager.getCodebasePrincipal(uri);
  64. if (uri.schemeIs("http")) {
  65. isFormSubmitHTTP = true;
  66. if (gContentSecurityManager.isOriginPotentiallyTrustworthy(principal)) {
  67. isFormSubmitSecure = true;
  68. }
  69. } else {
  70. isFormSubmitSecure = true;
  71. }
  72. }
  73. return { isFormSubmitHTTP, isFormSubmitSecure };
  74. },
  75. /**
  76. * Checks if there are insecure password fields present on the form's document
  77. * i.e. passwords inside forms with http action, inside iframes with http src,
  78. * or on insecure web pages.
  79. *
  80. * @param {FormLike} aForm A form-like object. @See {LoginFormFactory}
  81. * @return {boolean} whether the form is secure
  82. */
  83. isFormSecure(aForm) {
  84. // Ignores window.opener, see top level documentation.
  85. let isSafePage = aForm.ownerDocument.defaultView.isSecureContextIfOpenerIgnored;
  86. let { isFormSubmitSecure, isFormSubmitHTTP } = this._checkFormSecurity(aForm);
  87. return isSafePage && (isFormSubmitSecure || !isFormSubmitHTTP);
  88. },
  89. /**
  90. * Report insecure password fields in a form to the web console to warn developers.
  91. *
  92. * @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
  93. */
  94. reportInsecurePasswords(aForm) {
  95. if (this._formRootsWarned.has(aForm.rootElement) ||
  96. this._formRootsWarned.get(aForm.rootElement)) {
  97. return;
  98. }
  99. let domDoc = aForm.ownerDocument;
  100. // Ignores window.opener, see top level documentation.
  101. let isSafePage = domDoc.defaultView.isSecureContextIfOpenerIgnored;
  102. let { isFormSubmitHTTP, isFormSubmitSecure } = this._checkFormSecurity(aForm);
  103. if (!isSafePage) {
  104. if (domDoc.defaultView == domDoc.defaultView.parent) {
  105. this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);
  106. } else {
  107. this._sendWebConsoleMessage("InsecurePasswordsPresentOnIframe", domDoc);
  108. }
  109. this._formRootsWarned.set(aForm.rootElement, true);
  110. } else if (isFormSubmitHTTP && !isFormSubmitSecure) {
  111. this._sendWebConsoleMessage("InsecureFormActionPasswordsPresent", domDoc);
  112. this._formRootsWarned.set(aForm.rootElement, true);
  113. }
  114. // The safety of a password field determined by the form action and the page protocol
  115. let passwordSafety;
  116. if (isSafePage) {
  117. if (isFormSubmitSecure) {
  118. passwordSafety = 0;
  119. } else if (isFormSubmitHTTP) {
  120. passwordSafety = 1;
  121. } else {
  122. passwordSafety = 2;
  123. }
  124. } else if (isFormSubmitSecure) {
  125. passwordSafety = 3;
  126. } else if (isFormSubmitHTTP) {
  127. passwordSafety = 4;
  128. } else {
  129. passwordSafety = 5;
  130. }
  131. Services.telemetry.getHistogramById("PWMGR_LOGIN_PAGE_SAFETY").add(passwordSafety);
  132. },
  133. };