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