michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: michael@0: var gDialog; michael@0: var gBundleBrand; michael@0: var gPKIBundle; michael@0: var gSSLStatus; michael@0: var gCert; michael@0: var gChecking; michael@0: var gBroken; michael@0: var gNeedReset; michael@0: var gSecHistogram; michael@0: var gNsISecTel; michael@0: michael@0: Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); michael@0: michael@0: function badCertListener() {} michael@0: badCertListener.prototype = { michael@0: getInterface: function (aIID) { michael@0: return this.QueryInterface(aIID); michael@0: }, michael@0: QueryInterface: function(aIID) { michael@0: if (aIID.equals(Components.interfaces.nsIBadCertListener2) || michael@0: aIID.equals(Components.interfaces.nsIInterfaceRequestor) || michael@0: aIID.equals(Components.interfaces.nsISupports)) michael@0: return this; michael@0: michael@0: throw Components.results.NS_ERROR_NO_INTERFACE; michael@0: }, michael@0: handle_test_result: function () { michael@0: if (gSSLStatus) michael@0: gCert = gSSLStatus.QueryInterface(Components.interfaces.nsISSLStatus).serverCert; michael@0: }, michael@0: notifyCertProblem: function MSR_notifyCertProblem(socketInfo, sslStatus, targetHost) { michael@0: gBroken = true; michael@0: gSSLStatus = sslStatus; michael@0: this.handle_test_result(); michael@0: return true; // suppress error UI michael@0: } michael@0: } michael@0: michael@0: function initExceptionDialog() { michael@0: gNeedReset = false; michael@0: gDialog = document.documentElement; michael@0: gBundleBrand = document.getElementById("brand_bundle"); michael@0: gPKIBundle = document.getElementById("pippki_bundle"); michael@0: gSecHistogram = Components.classes["@mozilla.org/base/telemetry;1"]. michael@0: getService(Components.interfaces.nsITelemetry). michael@0: getHistogramById("SECURITY_UI"); michael@0: gNsISecTel = Components.interfaces.nsISecurityUITelemetry; michael@0: michael@0: var brandName = gBundleBrand.getString("brandShortName"); michael@0: setText("warningText", gPKIBundle.getFormattedString("addExceptionBrandedWarning2", [brandName])); michael@0: gDialog.getButton("extra1").disabled = true; michael@0: michael@0: var args = window.arguments; michael@0: if (args && args[0]) { michael@0: if (args[0].location) { michael@0: // We were pre-seeded with a location. michael@0: document.getElementById("locationTextBox").value = args[0].location; michael@0: document.getElementById('checkCertButton').disabled = false; michael@0: michael@0: // We can optionally pre-fetch the certificate too. Don't do this michael@0: // synchronously, since it would prevent the window from appearing michael@0: // until the fetch is completed, which could be multiple seconds. michael@0: // Instead, let's use a timer to spawn the actual fetch, but update michael@0: // the dialog to "checking..." state right away, so that the UI michael@0: // is appropriately responsive. Bug 453855 michael@0: if (args[0].prefetchCert) { michael@0: michael@0: document.getElementById("checkCertButton").disabled = true; michael@0: gChecking = true; michael@0: updateCertStatus(); michael@0: michael@0: window.setTimeout(checkCert, 0); michael@0: } michael@0: } michael@0: michael@0: // Set out parameter to false by default michael@0: args[0].exceptionAdded = false; michael@0: } michael@0: } michael@0: michael@0: // returns true if found and global status could be set michael@0: function findRecentBadCert(uri) { michael@0: try { michael@0: var certDB = Components.classes["@mozilla.org/security/x509certdb;1"] michael@0: .getService(Components.interfaces.nsIX509CertDB); michael@0: if (!certDB) michael@0: return false; michael@0: var recentCertsSvc = certDB.getRecentBadCerts(inPrivateBrowsingMode()); michael@0: if (!recentCertsSvc) michael@0: return false; michael@0: michael@0: var hostWithPort = uri.host + ":" + uri.port; michael@0: gSSLStatus = recentCertsSvc.getRecentBadCert(hostWithPort); michael@0: if (!gSSLStatus) michael@0: return false; michael@0: michael@0: gCert = gSSLStatus.QueryInterface(Components.interfaces.nsISSLStatus).serverCert; michael@0: if (!gCert) michael@0: return false; michael@0: michael@0: gBroken = true; michael@0: } michael@0: catch (e) { michael@0: return false; michael@0: } michael@0: updateCertStatus(); michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Attempt to download the certificate for the location specified, and populate michael@0: * the Certificate Status section with the result. michael@0: */ michael@0: function checkCert() { michael@0: michael@0: gCert = null; michael@0: gSSLStatus = null; michael@0: gChecking = true; michael@0: gBroken = false; michael@0: updateCertStatus(); michael@0: michael@0: var uri = getURI(); michael@0: michael@0: // Is the cert already known in the list of recently seen bad certs? michael@0: if (findRecentBadCert(uri) == true) michael@0: return; michael@0: michael@0: var req = new XMLHttpRequest(); michael@0: try { michael@0: if(uri) { michael@0: req.open('GET', uri.prePath, false); michael@0: req.channel.notificationCallbacks = new badCertListener(); michael@0: req.send(null); michael@0: } michael@0: } catch (e) { michael@0: // We *expect* exceptions if there are problems with the certificate michael@0: // presented by the site. Log it, just in case, but we can proceed here, michael@0: // with appropriate sanity checks michael@0: Components.utils.reportError("Attempted to connect to a site with a bad certificate in the add exception dialog. " + michael@0: "This results in a (mostly harmless) exception being thrown. " + michael@0: "Logged for information purposes only: " + e); michael@0: } finally { michael@0: gChecking = false; michael@0: } michael@0: michael@0: if(req.channel && req.channel.securityInfo) { michael@0: const Ci = Components.interfaces; michael@0: gSSLStatus = req.channel.securityInfo michael@0: .QueryInterface(Ci.nsISSLStatusProvider).SSLStatus; michael@0: gCert = gSSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert; michael@0: } michael@0: updateCertStatus(); michael@0: } michael@0: michael@0: /** michael@0: * Build and return a URI, based on the information supplied in the michael@0: * Certificate Location fields michael@0: */ michael@0: function getURI() { michael@0: // Use fixup service instead of just ioservice's newURI since it's quite likely michael@0: // that the host will be supplied without a protocol prefix, resulting in malformed michael@0: // uri exceptions being thrown. michael@0: var fus = Components.classes["@mozilla.org/docshell/urifixup;1"] michael@0: .getService(Components.interfaces.nsIURIFixup); michael@0: var uri = fus.createFixupURI(document.getElementById("locationTextBox").value, 0); michael@0: michael@0: if(!uri) michael@0: return null; michael@0: michael@0: if(uri.scheme == "http") michael@0: uri.scheme = "https"; michael@0: michael@0: if (uri.port == -1) michael@0: uri.port = 443; michael@0: michael@0: return uri; michael@0: } michael@0: michael@0: function resetDialog() { michael@0: document.getElementById("viewCertButton").disabled = true; michael@0: document.getElementById("permanent").disabled = true; michael@0: gDialog.getButton("extra1").disabled = true; michael@0: setText("headerDescription", ""); michael@0: setText("statusDescription", ""); michael@0: setText("statusLongDescription", ""); michael@0: setText("status2Description", ""); michael@0: setText("status2LongDescription", ""); michael@0: setText("status3Description", ""); michael@0: setText("status3LongDescription", ""); michael@0: } michael@0: michael@0: /** michael@0: * Called by input textboxes to manage UI state michael@0: */ michael@0: function handleTextChange() { michael@0: var checkCertButton = document.getElementById('checkCertButton'); michael@0: checkCertButton.disabled = !(document.getElementById("locationTextBox").value); michael@0: if (gNeedReset) { michael@0: gNeedReset = false; michael@0: resetDialog(); michael@0: } michael@0: } michael@0: michael@0: function updateCertStatus() { michael@0: var shortDesc, longDesc; michael@0: var shortDesc2, longDesc2; michael@0: var shortDesc3, longDesc3; michael@0: var use2 = false; michael@0: var use3 = false; michael@0: let bucketId = gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_BASE; michael@0: if(gCert) { michael@0: if(gBroken) { michael@0: var mms = "addExceptionDomainMismatchShort"; michael@0: var mml = "addExceptionDomainMismatchLong"; michael@0: var exs = "addExceptionExpiredShort"; michael@0: var exl = "addExceptionExpiredLong"; michael@0: var uts = "addExceptionUnverifiedOrBadSignatureShort"; michael@0: var utl = "addExceptionUnverifiedOrBadSignatureLong"; michael@0: var use1 = false; michael@0: if (gSSLStatus.isDomainMismatch) { michael@0: bucketId += gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_DOMAIN; michael@0: use1 = true; michael@0: shortDesc = mms; michael@0: longDesc = mml; michael@0: } michael@0: if (gSSLStatus.isNotValidAtThisTime) { michael@0: bucketId += gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_TIME; michael@0: if (!use1) { michael@0: use1 = true; michael@0: shortDesc = exs; michael@0: longDesc = exl; michael@0: } michael@0: else { michael@0: use2 = true; michael@0: shortDesc2 = exs; michael@0: longDesc2 = exl; michael@0: } michael@0: } michael@0: if (gSSLStatus.isUntrusted) { michael@0: bucketId += gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_UNTRUSTED; michael@0: if (!use1) { michael@0: use1 = true; michael@0: shortDesc = uts; michael@0: longDesc = utl; michael@0: } michael@0: else if (!use2) { michael@0: use2 = true; michael@0: shortDesc2 = uts; michael@0: longDesc2 = utl; michael@0: } michael@0: else { michael@0: use3 = true; michael@0: shortDesc3 = uts; michael@0: longDesc3 = utl; michael@0: } michael@0: } michael@0: gSecHistogram.add(bucketId); michael@0: michael@0: // In these cases, we do want to enable the "Add Exception" button michael@0: gDialog.getButton("extra1").disabled = false; michael@0: michael@0: // If the Private Browsing service is available and the mode is active, michael@0: // don't store permanent exceptions, since they would persist after michael@0: // private browsing mode was disabled. michael@0: var inPrivateBrowsing = inPrivateBrowsingMode(); michael@0: var pe = document.getElementById("permanent"); michael@0: pe.disabled = inPrivateBrowsing; michael@0: pe.checked = !inPrivateBrowsing; michael@0: michael@0: setText("headerDescription", gPKIBundle.getString("addExceptionInvalidHeader")); michael@0: } michael@0: else { michael@0: shortDesc = "addExceptionValidShort"; michael@0: longDesc = "addExceptionValidLong"; michael@0: gDialog.getButton("extra1").disabled = true; michael@0: document.getElementById("permanent").disabled = true; michael@0: } michael@0: michael@0: // We're done checking the certificate, so allow the user to check it again. michael@0: document.getElementById("checkCertButton").disabled = false; michael@0: document.getElementById("viewCertButton").disabled = false; michael@0: michael@0: // Notify observers about the availability of the certificate michael@0: Components.classes["@mozilla.org/observer-service;1"] michael@0: .getService(Components.interfaces.nsIObserverService) michael@0: .notifyObservers(null, "cert-exception-ui-ready", null); michael@0: } michael@0: else if (gChecking) { michael@0: shortDesc = "addExceptionCheckingShort"; michael@0: longDesc = "addExceptionCheckingLong"; michael@0: // We're checking the certificate, so we disable the Get Certificate michael@0: // button to make sure that the user can't interrupt the process and michael@0: // trigger another certificate fetch. michael@0: document.getElementById("checkCertButton").disabled = true; michael@0: document.getElementById("viewCertButton").disabled = true; michael@0: gDialog.getButton("extra1").disabled = true; michael@0: document.getElementById("permanent").disabled = true; michael@0: } michael@0: else { michael@0: shortDesc = "addExceptionNoCertShort"; michael@0: longDesc = "addExceptionNoCertLong"; michael@0: // We're done checking the certificate, so allow the user to check it again. michael@0: document.getElementById("checkCertButton").disabled = false; michael@0: document.getElementById("viewCertButton").disabled = true; michael@0: gDialog.getButton("extra1").disabled = true; michael@0: document.getElementById("permanent").disabled = true; michael@0: } michael@0: michael@0: setText("statusDescription", gPKIBundle.getString(shortDesc)); michael@0: setText("statusLongDescription", gPKIBundle.getString(longDesc)); michael@0: michael@0: if (use2) { michael@0: setText("status2Description", gPKIBundle.getString(shortDesc2)); michael@0: setText("status2LongDescription", gPKIBundle.getString(longDesc2)); michael@0: } michael@0: michael@0: if (use3) { michael@0: setText("status3Description", gPKIBundle.getString(shortDesc3)); michael@0: setText("status3LongDescription", gPKIBundle.getString(longDesc3)); michael@0: } michael@0: michael@0: gNeedReset = true; michael@0: } michael@0: michael@0: /** michael@0: * Handle user request to display certificate details michael@0: */ michael@0: function viewCertButtonClick() { michael@0: gSecHistogram.add(gNsISecTel.WARNING_BAD_CERT_TOP_CLICK_VIEW_CERT); michael@0: if (gCert) michael@0: viewCertHelper(this, gCert); michael@0: michael@0: } michael@0: michael@0: /** michael@0: * Handle user request to add an exception for the specified cert michael@0: */ michael@0: function addException() { michael@0: if(!gCert || !gSSLStatus) michael@0: return; michael@0: michael@0: var overrideService = Components.classes["@mozilla.org/security/certoverride;1"] michael@0: .getService(Components.interfaces.nsICertOverrideService); michael@0: var flags = 0; michael@0: let confirmBucketId = gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_BASE; michael@0: if (gSSLStatus.isUntrusted) { michael@0: flags |= overrideService.ERROR_UNTRUSTED; michael@0: confirmBucketId += gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_UNTRUSTED; michael@0: } michael@0: if (gSSLStatus.isDomainMismatch) { michael@0: flags |= overrideService.ERROR_MISMATCH; michael@0: confirmBucketId += gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_DOMAIN; michael@0: } michael@0: if (gSSLStatus.isNotValidAtThisTime) { michael@0: flags |= overrideService.ERROR_TIME; michael@0: confirmBucketId += gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_TIME; michael@0: } michael@0: michael@0: var permanentCheckbox = document.getElementById("permanent"); michael@0: var shouldStorePermanently = permanentCheckbox.checked && !inPrivateBrowsingMode(); michael@0: if(!permanentCheckbox.checked) michael@0: gSecHistogram.add(gNsISecTel.WARNING_BAD_CERT_TOP_DONT_REMEMBER_EXCEPTION); michael@0: michael@0: gSecHistogram.add(confirmBucketId); michael@0: var uri = getURI(); michael@0: overrideService.rememberValidityOverride( michael@0: uri.asciiHost, uri.port, michael@0: gCert, michael@0: flags, michael@0: !shouldStorePermanently); michael@0: michael@0: var args = window.arguments; michael@0: if (args && args[0]) michael@0: args[0].exceptionAdded = true; michael@0: michael@0: gDialog.acceptDialog(); michael@0: } michael@0: michael@0: /** michael@0: * Returns true if this dialog is in private browsing mode. michael@0: */ michael@0: function inPrivateBrowsingMode() { michael@0: return PrivateBrowsingUtils.isWindowPrivate(window); michael@0: }