gecko/security/manager/pki/resources/content/exceptionDialog.js
Ehsan Akhgari 115ba5a2eb Bug 722978 - Port the certificate exception UI to the new per-window private browsing API; r=jdm
--HG--
extra : rebase_source : a49d0c630a170a950dd830ad484e084a29f8b51f
2012-10-12 23:38:19 -04:00

389 lines
13 KiB
JavaScript

/* 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 = srGetStrBundle("chrome://branding/locale/brand.properties");
gPKIBundle = srGetStrBundle("chrome://pippki/locale/pippki.properties");
gSecHistogram = Components.classes["@mozilla.org/base/telemetry;1"].
getService(Components.interfaces.nsITelemetry).
getHistogramById("SECURITY_UI");
gNsISecTel = Components.interfaces.nsISecurityUITelemetry;
var brandName = gBundleBrand.GetStringFromName("brandShortName");
setText("warningText", gPKIBundle.formatStringFromName("addExceptionBrandedWarning2",
[brandName], 1));
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 recentCertsSvc = Components.classes["@mozilla.org/security/recentbadcerts;1"]
.getService(Components.interfaces.nsIRecentBadCertsService);
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_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_ADD_EXCEPTION_FLAG_DOMAIN;
use1 = true;
shortDesc = mms;
longDesc = mml;
}
if (gSSLStatus.isNotValidAtThisTime) {
bucketId += gNsISecTel.WARNING_BAD_CERT_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_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.GetStringFromName("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.GetStringFromName(shortDesc));
setText("statusLongDescription", gPKIBundle.GetStringFromName(longDesc));
if (use2) {
setText("status2Description", gPKIBundle.GetStringFromName(shortDesc2));
setText("status2LongDescription", gPKIBundle.GetStringFromName(longDesc2));
}
if (use3) {
setText("status3Description", gPKIBundle.GetStringFromName(shortDesc3));
setText("status3LongDescription", gPKIBundle.GetStringFromName(longDesc3));
}
gNeedReset = true;
}
/**
* Handle user request to display certificate details
*/
function viewCertButtonClick() {
gSecHistogram.add(gNsISecTel.WARNING_BAD_CERT_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_CONFIRM_ADD_EXCEPTION_BASE;
if (gSSLStatus.isUntrusted) {
flags |= overrideService.ERROR_UNTRUSTED;
confirmBucketId += gNsISecTel.WARNING_BAD_CERT_CONFIRM_ADD_EXCEPTION_FLAG_UNTRUSTED;
}
if (gSSLStatus.isDomainMismatch) {
flags |= overrideService.ERROR_MISMATCH;
confirmBucketId += gNsISecTel.WARNING_BAD_CERT_CONFIRM_ADD_EXCEPTION_FLAG_DOMAIN;
}
if (gSSLStatus.isNotValidAtThisTime) {
flags |= overrideService.ERROR_TIME;
confirmBucketId += gNsISecTel.WARNING_BAD_CERT_CONFIRM_ADD_EXCEPTION_FLAG_TIME;
}
var permanentCheckbox = document.getElementById("permanent");
var shouldStorePermanently = permanentCheckbox.checked && !inPrivateBrowsingMode();
if(!permanentCheckbox.checked)
gSecHistogram.add(gNsISecTel.WARNING_BAD_CERT_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);
}