Source code

Revision control

Other Tools

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
5
/* ownerGlobal doesn't exist in content privileged windows. */
6
/* eslint-disable mozilla/use-ownerGlobal */
7
8
const EXPORTED_SYMBOLS = ["InsecurePasswordUtils"];
9
11
12
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
13
const { XPCOMUtils } = ChromeUtils.import(
15
);
16
17
XPCOMUtils.defineLazyServiceGetter(
18
this,
19
"gContentSecurityManager",
20
"@mozilla.org/contentsecuritymanager;1",
21
"nsIContentSecurityManager"
22
);
23
XPCOMUtils.defineLazyServiceGetter(
24
this,
25
"gScriptSecurityManager",
26
"@mozilla.org/scriptsecuritymanager;1",
27
"nsIScriptSecurityManager"
28
);
29
ChromeUtils.defineModuleGetter(
30
this,
31
"LoginHelper",
33
);
34
35
XPCOMUtils.defineLazyGetter(this, "log", () => {
36
return LoginHelper.createLogger("InsecurePasswordUtils");
37
});
38
39
/*
40
* A module that provides utility functions for form security.
41
*
42
*/
43
this.InsecurePasswordUtils = {
44
_formRootsWarned: new WeakMap(),
45
46
/**
47
* Gets the ID of the inner window of this DOM window.
48
*
49
* @param nsIDOMWindow window
50
* @return integer
51
* Inner ID for the given window.
52
*/
53
_getInnerWindowId(window) {
54
return window.windowUtils.currentInnerWindowID;
55
},
56
57
_sendWebConsoleMessage(messageTag, domDoc) {
58
let windowId = this._getInnerWindowId(domDoc.defaultView);
59
let category = "Insecure Password Field";
60
// All web console messages are warnings for now.
61
let flag = Ci.nsIScriptError.warningFlag;
62
let bundle = Services.strings.createBundle(STRINGS_URI);
63
let message = bundle.GetStringFromName(messageTag);
64
let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(
65
Ci.nsIScriptError
66
);
67
consoleMsg.initWithWindowID(
68
message,
69
domDoc.location.href,
70
0,
71
0,
72
0,
73
flag,
74
category,
75
windowId
76
);
77
78
Services.console.logMessage(consoleMsg);
79
},
80
81
/**
82
* Gets the security state of the passed form.
83
*
84
* @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
85
*
86
* @returns {Object} An object with the following boolean values:
87
* isFormSubmitHTTP: if the submit action is an http:// URL
88
* isFormSubmitSecure: if the submit action URL is secure,
89
* either because it is HTTPS or because its origin is considered trustworthy
90
*/
91
_checkFormSecurity(aForm) {
92
let isFormSubmitHTTP = false,
93
isFormSubmitSecure = false;
94
if (ChromeUtils.getClassName(aForm.rootElement) === "HTMLFormElement") {
95
let uri = Services.io.newURI(
96
aForm.rootElement.action || aForm.rootElement.baseURI
97
);
98
let principal = gScriptSecurityManager.createContentPrincipal(uri, {});
99
100
if (uri.schemeIs("http")) {
101
isFormSubmitHTTP = true;
102
if (
103
principal.IsOriginPotentiallyTrustworthy ||
104
// Ignore sites with local IP addresses pointing to local forms.
105
(this._isPrincipalForLocalIPAddress(
106
aForm.rootElement.nodePrincipal
107
) &&
108
this._isPrincipalForLocalIPAddress(principal))
109
) {
110
isFormSubmitSecure = true;
111
}
112
} else {
113
isFormSubmitSecure = true;
114
}
115
}
116
117
return { isFormSubmitHTTP, isFormSubmitSecure };
118
},
119
120
_isPrincipalForLocalIPAddress(aPrincipal) {
121
try {
122
let uri = aPrincipal.URI;
123
if (Services.io.hostnameIsLocalIPAddress(uri)) {
124
log.debug("hasInsecureLoginForms: detected local IP address:", uri);
125
return true;
126
}
127
} catch (e) {
128
log.debug(
129
"hasInsecureLoginForms: unable to check for local IP address:",
130
e
131
);
132
}
133
return false;
134
},
135
136
/**
137
* Checks if there are insecure password fields present on the form's document
138
* i.e. passwords inside forms with http action, inside iframes with http src,
139
* or on insecure web pages.
140
*
141
* @param {FormLike} aForm A form-like object. @See {LoginFormFactory}
142
* @return {boolean} whether the form is secure
143
*/
144
isFormSecure(aForm) {
145
let isSafePage = aForm.ownerDocument.defaultView.isSecureContext;
146
147
// Ignore insecure documents with URLs that are local IP addresses.
148
// This is done because the vast majority of routers and other devices
149
// on the network do not use HTTPS, making this warning show up almost
150
// constantly on local connections, which annoys users and hurts our cause.
151
if (!isSafePage && this._ignoreLocalIPAddress) {
152
let isLocalIP = this._isPrincipalForLocalIPAddress(
153
aForm.rootElement.nodePrincipal
154
);
155
// XXXndeakin fix this: bug 1582499 - top document not accessible in OOP frame
156
// So for now, just use the current document if access to top fails.
157
let topDocument;
158
try {
159
topDocument = aForm.ownerDocument.defaultView.top.document;
160
} catch (ex) {
161
topDocument = aForm.ownerDocument.defaultView.document;
162
}
163
let topIsLocalIP = this._isPrincipalForLocalIPAddress(
164
topDocument.nodePrincipal
165
);
166
167
// Only consider the page safe if the top window has a local IP address
168
// and, if this is an iframe, the iframe also has a local IP address.
169
if (isLocalIP && topIsLocalIP) {
170
isSafePage = true;
171
}
172
}
173
174
let { isFormSubmitSecure, isFormSubmitHTTP } = this._checkFormSecurity(
175
aForm
176
);
177
178
return isSafePage && (isFormSubmitSecure || !isFormSubmitHTTP);
179
},
180
181
/**
182
* Report insecure password fields in a form to the web console to warn developers.
183
*
184
* @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
185
*/
186
reportInsecurePasswords(aForm) {
187
if (
188
this._formRootsWarned.has(aForm.rootElement) ||
189
this._formRootsWarned.get(aForm.rootElement)
190
) {
191
return;
192
}
193
194
let domDoc = aForm.ownerDocument;
195
let isSafePage = domDoc.defaultView.isSecureContext;
196
197
let { isFormSubmitHTTP, isFormSubmitSecure } = this._checkFormSecurity(
198
aForm
199
);
200
201
if (!isSafePage) {
202
if (domDoc.defaultView == domDoc.defaultView.parent) {
203
this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);
204
} else {
205
this._sendWebConsoleMessage("InsecurePasswordsPresentOnIframe", domDoc);
206
}
207
this._formRootsWarned.set(aForm.rootElement, true);
208
} else if (isFormSubmitHTTP && !isFormSubmitSecure) {
209
this._sendWebConsoleMessage("InsecureFormActionPasswordsPresent", domDoc);
210
this._formRootsWarned.set(aForm.rootElement, true);
211
}
212
213
// The safety of a password field determined by the form action and the page protocol
214
let passwordSafety;
215
if (isSafePage) {
216
if (isFormSubmitSecure) {
217
passwordSafety = 0;
218
} else if (isFormSubmitHTTP) {
219
passwordSafety = 1;
220
} else {
221
passwordSafety = 2;
222
}
223
} else if (isFormSubmitSecure) {
224
passwordSafety = 3;
225
} else if (isFormSubmitHTTP) {
226
passwordSafety = 4;
227
} else {
228
passwordSafety = 5;
229
}
230
231
Services.telemetry
232
.getHistogramById("PWMGR_LOGIN_PAGE_SAFETY")
233
.add(passwordSafety);
234
},
235
};
236
237
XPCOMUtils.defineLazyPreferenceGetter(
238
this.InsecurePasswordUtils,
239
"_ignoreLocalIPAddress",
240
"security.insecure_field_warning.ignore_local_ip_address",
241
true
242
);