|
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 |
|
6 var gDialog; |
|
7 var gBundleBrand; |
|
8 var gPKIBundle; |
|
9 var gSSLStatus; |
|
10 var gCert; |
|
11 var gChecking; |
|
12 var gBroken; |
|
13 var gNeedReset; |
|
14 var gSecHistogram; |
|
15 var gNsISecTel; |
|
16 |
|
17 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); |
|
18 |
|
19 function badCertListener() {} |
|
20 badCertListener.prototype = { |
|
21 getInterface: function (aIID) { |
|
22 return this.QueryInterface(aIID); |
|
23 }, |
|
24 QueryInterface: function(aIID) { |
|
25 if (aIID.equals(Components.interfaces.nsIBadCertListener2) || |
|
26 aIID.equals(Components.interfaces.nsIInterfaceRequestor) || |
|
27 aIID.equals(Components.interfaces.nsISupports)) |
|
28 return this; |
|
29 |
|
30 throw Components.results.NS_ERROR_NO_INTERFACE; |
|
31 }, |
|
32 handle_test_result: function () { |
|
33 if (gSSLStatus) |
|
34 gCert = gSSLStatus.QueryInterface(Components.interfaces.nsISSLStatus).serverCert; |
|
35 }, |
|
36 notifyCertProblem: function MSR_notifyCertProblem(socketInfo, sslStatus, targetHost) { |
|
37 gBroken = true; |
|
38 gSSLStatus = sslStatus; |
|
39 this.handle_test_result(); |
|
40 return true; // suppress error UI |
|
41 } |
|
42 } |
|
43 |
|
44 function initExceptionDialog() { |
|
45 gNeedReset = false; |
|
46 gDialog = document.documentElement; |
|
47 gBundleBrand = document.getElementById("brand_bundle"); |
|
48 gPKIBundle = document.getElementById("pippki_bundle"); |
|
49 gSecHistogram = Components.classes["@mozilla.org/base/telemetry;1"]. |
|
50 getService(Components.interfaces.nsITelemetry). |
|
51 getHistogramById("SECURITY_UI"); |
|
52 gNsISecTel = Components.interfaces.nsISecurityUITelemetry; |
|
53 |
|
54 var brandName = gBundleBrand.getString("brandShortName"); |
|
55 setText("warningText", gPKIBundle.getFormattedString("addExceptionBrandedWarning2", [brandName])); |
|
56 gDialog.getButton("extra1").disabled = true; |
|
57 |
|
58 var args = window.arguments; |
|
59 if (args && args[0]) { |
|
60 if (args[0].location) { |
|
61 // We were pre-seeded with a location. |
|
62 document.getElementById("locationTextBox").value = args[0].location; |
|
63 document.getElementById('checkCertButton').disabled = false; |
|
64 |
|
65 // We can optionally pre-fetch the certificate too. Don't do this |
|
66 // synchronously, since it would prevent the window from appearing |
|
67 // until the fetch is completed, which could be multiple seconds. |
|
68 // Instead, let's use a timer to spawn the actual fetch, but update |
|
69 // the dialog to "checking..." state right away, so that the UI |
|
70 // is appropriately responsive. Bug 453855 |
|
71 if (args[0].prefetchCert) { |
|
72 |
|
73 document.getElementById("checkCertButton").disabled = true; |
|
74 gChecking = true; |
|
75 updateCertStatus(); |
|
76 |
|
77 window.setTimeout(checkCert, 0); |
|
78 } |
|
79 } |
|
80 |
|
81 // Set out parameter to false by default |
|
82 args[0].exceptionAdded = false; |
|
83 } |
|
84 } |
|
85 |
|
86 // returns true if found and global status could be set |
|
87 function findRecentBadCert(uri) { |
|
88 try { |
|
89 var certDB = Components.classes["@mozilla.org/security/x509certdb;1"] |
|
90 .getService(Components.interfaces.nsIX509CertDB); |
|
91 if (!certDB) |
|
92 return false; |
|
93 var recentCertsSvc = certDB.getRecentBadCerts(inPrivateBrowsingMode()); |
|
94 if (!recentCertsSvc) |
|
95 return false; |
|
96 |
|
97 var hostWithPort = uri.host + ":" + uri.port; |
|
98 gSSLStatus = recentCertsSvc.getRecentBadCert(hostWithPort); |
|
99 if (!gSSLStatus) |
|
100 return false; |
|
101 |
|
102 gCert = gSSLStatus.QueryInterface(Components.interfaces.nsISSLStatus).serverCert; |
|
103 if (!gCert) |
|
104 return false; |
|
105 |
|
106 gBroken = true; |
|
107 } |
|
108 catch (e) { |
|
109 return false; |
|
110 } |
|
111 updateCertStatus(); |
|
112 return true; |
|
113 } |
|
114 |
|
115 /** |
|
116 * Attempt to download the certificate for the location specified, and populate |
|
117 * the Certificate Status section with the result. |
|
118 */ |
|
119 function checkCert() { |
|
120 |
|
121 gCert = null; |
|
122 gSSLStatus = null; |
|
123 gChecking = true; |
|
124 gBroken = false; |
|
125 updateCertStatus(); |
|
126 |
|
127 var uri = getURI(); |
|
128 |
|
129 // Is the cert already known in the list of recently seen bad certs? |
|
130 if (findRecentBadCert(uri) == true) |
|
131 return; |
|
132 |
|
133 var req = new XMLHttpRequest(); |
|
134 try { |
|
135 if(uri) { |
|
136 req.open('GET', uri.prePath, false); |
|
137 req.channel.notificationCallbacks = new badCertListener(); |
|
138 req.send(null); |
|
139 } |
|
140 } catch (e) { |
|
141 // We *expect* exceptions if there are problems with the certificate |
|
142 // presented by the site. Log it, just in case, but we can proceed here, |
|
143 // with appropriate sanity checks |
|
144 Components.utils.reportError("Attempted to connect to a site with a bad certificate in the add exception dialog. " + |
|
145 "This results in a (mostly harmless) exception being thrown. " + |
|
146 "Logged for information purposes only: " + e); |
|
147 } finally { |
|
148 gChecking = false; |
|
149 } |
|
150 |
|
151 if(req.channel && req.channel.securityInfo) { |
|
152 const Ci = Components.interfaces; |
|
153 gSSLStatus = req.channel.securityInfo |
|
154 .QueryInterface(Ci.nsISSLStatusProvider).SSLStatus; |
|
155 gCert = gSSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert; |
|
156 } |
|
157 updateCertStatus(); |
|
158 } |
|
159 |
|
160 /** |
|
161 * Build and return a URI, based on the information supplied in the |
|
162 * Certificate Location fields |
|
163 */ |
|
164 function getURI() { |
|
165 // Use fixup service instead of just ioservice's newURI since it's quite likely |
|
166 // that the host will be supplied without a protocol prefix, resulting in malformed |
|
167 // uri exceptions being thrown. |
|
168 var fus = Components.classes["@mozilla.org/docshell/urifixup;1"] |
|
169 .getService(Components.interfaces.nsIURIFixup); |
|
170 var uri = fus.createFixupURI(document.getElementById("locationTextBox").value, 0); |
|
171 |
|
172 if(!uri) |
|
173 return null; |
|
174 |
|
175 if(uri.scheme == "http") |
|
176 uri.scheme = "https"; |
|
177 |
|
178 if (uri.port == -1) |
|
179 uri.port = 443; |
|
180 |
|
181 return uri; |
|
182 } |
|
183 |
|
184 function resetDialog() { |
|
185 document.getElementById("viewCertButton").disabled = true; |
|
186 document.getElementById("permanent").disabled = true; |
|
187 gDialog.getButton("extra1").disabled = true; |
|
188 setText("headerDescription", ""); |
|
189 setText("statusDescription", ""); |
|
190 setText("statusLongDescription", ""); |
|
191 setText("status2Description", ""); |
|
192 setText("status2LongDescription", ""); |
|
193 setText("status3Description", ""); |
|
194 setText("status3LongDescription", ""); |
|
195 } |
|
196 |
|
197 /** |
|
198 * Called by input textboxes to manage UI state |
|
199 */ |
|
200 function handleTextChange() { |
|
201 var checkCertButton = document.getElementById('checkCertButton'); |
|
202 checkCertButton.disabled = !(document.getElementById("locationTextBox").value); |
|
203 if (gNeedReset) { |
|
204 gNeedReset = false; |
|
205 resetDialog(); |
|
206 } |
|
207 } |
|
208 |
|
209 function updateCertStatus() { |
|
210 var shortDesc, longDesc; |
|
211 var shortDesc2, longDesc2; |
|
212 var shortDesc3, longDesc3; |
|
213 var use2 = false; |
|
214 var use3 = false; |
|
215 let bucketId = gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_BASE; |
|
216 if(gCert) { |
|
217 if(gBroken) { |
|
218 var mms = "addExceptionDomainMismatchShort"; |
|
219 var mml = "addExceptionDomainMismatchLong"; |
|
220 var exs = "addExceptionExpiredShort"; |
|
221 var exl = "addExceptionExpiredLong"; |
|
222 var uts = "addExceptionUnverifiedOrBadSignatureShort"; |
|
223 var utl = "addExceptionUnverifiedOrBadSignatureLong"; |
|
224 var use1 = false; |
|
225 if (gSSLStatus.isDomainMismatch) { |
|
226 bucketId += gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_DOMAIN; |
|
227 use1 = true; |
|
228 shortDesc = mms; |
|
229 longDesc = mml; |
|
230 } |
|
231 if (gSSLStatus.isNotValidAtThisTime) { |
|
232 bucketId += gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_TIME; |
|
233 if (!use1) { |
|
234 use1 = true; |
|
235 shortDesc = exs; |
|
236 longDesc = exl; |
|
237 } |
|
238 else { |
|
239 use2 = true; |
|
240 shortDesc2 = exs; |
|
241 longDesc2 = exl; |
|
242 } |
|
243 } |
|
244 if (gSSLStatus.isUntrusted) { |
|
245 bucketId += gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_UNTRUSTED; |
|
246 if (!use1) { |
|
247 use1 = true; |
|
248 shortDesc = uts; |
|
249 longDesc = utl; |
|
250 } |
|
251 else if (!use2) { |
|
252 use2 = true; |
|
253 shortDesc2 = uts; |
|
254 longDesc2 = utl; |
|
255 } |
|
256 else { |
|
257 use3 = true; |
|
258 shortDesc3 = uts; |
|
259 longDesc3 = utl; |
|
260 } |
|
261 } |
|
262 gSecHistogram.add(bucketId); |
|
263 |
|
264 // In these cases, we do want to enable the "Add Exception" button |
|
265 gDialog.getButton("extra1").disabled = false; |
|
266 |
|
267 // If the Private Browsing service is available and the mode is active, |
|
268 // don't store permanent exceptions, since they would persist after |
|
269 // private browsing mode was disabled. |
|
270 var inPrivateBrowsing = inPrivateBrowsingMode(); |
|
271 var pe = document.getElementById("permanent"); |
|
272 pe.disabled = inPrivateBrowsing; |
|
273 pe.checked = !inPrivateBrowsing; |
|
274 |
|
275 setText("headerDescription", gPKIBundle.getString("addExceptionInvalidHeader")); |
|
276 } |
|
277 else { |
|
278 shortDesc = "addExceptionValidShort"; |
|
279 longDesc = "addExceptionValidLong"; |
|
280 gDialog.getButton("extra1").disabled = true; |
|
281 document.getElementById("permanent").disabled = true; |
|
282 } |
|
283 |
|
284 // We're done checking the certificate, so allow the user to check it again. |
|
285 document.getElementById("checkCertButton").disabled = false; |
|
286 document.getElementById("viewCertButton").disabled = false; |
|
287 |
|
288 // Notify observers about the availability of the certificate |
|
289 Components.classes["@mozilla.org/observer-service;1"] |
|
290 .getService(Components.interfaces.nsIObserverService) |
|
291 .notifyObservers(null, "cert-exception-ui-ready", null); |
|
292 } |
|
293 else if (gChecking) { |
|
294 shortDesc = "addExceptionCheckingShort"; |
|
295 longDesc = "addExceptionCheckingLong"; |
|
296 // We're checking the certificate, so we disable the Get Certificate |
|
297 // button to make sure that the user can't interrupt the process and |
|
298 // trigger another certificate fetch. |
|
299 document.getElementById("checkCertButton").disabled = true; |
|
300 document.getElementById("viewCertButton").disabled = true; |
|
301 gDialog.getButton("extra1").disabled = true; |
|
302 document.getElementById("permanent").disabled = true; |
|
303 } |
|
304 else { |
|
305 shortDesc = "addExceptionNoCertShort"; |
|
306 longDesc = "addExceptionNoCertLong"; |
|
307 // We're done checking the certificate, so allow the user to check it again. |
|
308 document.getElementById("checkCertButton").disabled = false; |
|
309 document.getElementById("viewCertButton").disabled = true; |
|
310 gDialog.getButton("extra1").disabled = true; |
|
311 document.getElementById("permanent").disabled = true; |
|
312 } |
|
313 |
|
314 setText("statusDescription", gPKIBundle.getString(shortDesc)); |
|
315 setText("statusLongDescription", gPKIBundle.getString(longDesc)); |
|
316 |
|
317 if (use2) { |
|
318 setText("status2Description", gPKIBundle.getString(shortDesc2)); |
|
319 setText("status2LongDescription", gPKIBundle.getString(longDesc2)); |
|
320 } |
|
321 |
|
322 if (use3) { |
|
323 setText("status3Description", gPKIBundle.getString(shortDesc3)); |
|
324 setText("status3LongDescription", gPKIBundle.getString(longDesc3)); |
|
325 } |
|
326 |
|
327 gNeedReset = true; |
|
328 } |
|
329 |
|
330 /** |
|
331 * Handle user request to display certificate details |
|
332 */ |
|
333 function viewCertButtonClick() { |
|
334 gSecHistogram.add(gNsISecTel.WARNING_BAD_CERT_TOP_CLICK_VIEW_CERT); |
|
335 if (gCert) |
|
336 viewCertHelper(this, gCert); |
|
337 |
|
338 } |
|
339 |
|
340 /** |
|
341 * Handle user request to add an exception for the specified cert |
|
342 */ |
|
343 function addException() { |
|
344 if(!gCert || !gSSLStatus) |
|
345 return; |
|
346 |
|
347 var overrideService = Components.classes["@mozilla.org/security/certoverride;1"] |
|
348 .getService(Components.interfaces.nsICertOverrideService); |
|
349 var flags = 0; |
|
350 let confirmBucketId = gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_BASE; |
|
351 if (gSSLStatus.isUntrusted) { |
|
352 flags |= overrideService.ERROR_UNTRUSTED; |
|
353 confirmBucketId += gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_UNTRUSTED; |
|
354 } |
|
355 if (gSSLStatus.isDomainMismatch) { |
|
356 flags |= overrideService.ERROR_MISMATCH; |
|
357 confirmBucketId += gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_DOMAIN; |
|
358 } |
|
359 if (gSSLStatus.isNotValidAtThisTime) { |
|
360 flags |= overrideService.ERROR_TIME; |
|
361 confirmBucketId += gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_TIME; |
|
362 } |
|
363 |
|
364 var permanentCheckbox = document.getElementById("permanent"); |
|
365 var shouldStorePermanently = permanentCheckbox.checked && !inPrivateBrowsingMode(); |
|
366 if(!permanentCheckbox.checked) |
|
367 gSecHistogram.add(gNsISecTel.WARNING_BAD_CERT_TOP_DONT_REMEMBER_EXCEPTION); |
|
368 |
|
369 gSecHistogram.add(confirmBucketId); |
|
370 var uri = getURI(); |
|
371 overrideService.rememberValidityOverride( |
|
372 uri.asciiHost, uri.port, |
|
373 gCert, |
|
374 flags, |
|
375 !shouldStorePermanently); |
|
376 |
|
377 var args = window.arguments; |
|
378 if (args && args[0]) |
|
379 args[0].exceptionAdded = true; |
|
380 |
|
381 gDialog.acceptDialog(); |
|
382 } |
|
383 |
|
384 /** |
|
385 * Returns true if this dialog is in private browsing mode. |
|
386 */ |
|
387 function inPrivateBrowsingMode() { |
|
388 return PrivateBrowsingUtils.isWindowPrivate(window); |
|
389 } |