diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index ecfd4fc93ac..1e87d4b0c06 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -90,6 +90,23 @@ pref("app.update.timer", 600000); // Enables some extra Application Update Logging (can reduce performance) pref("app.update.log", false); +// When |app.update.cert.requireBuiltIn| is true or not specified the +// final certificate and all certificates the connection is redirected to before +// the final certificate for the url specified in the |app.update.url| +// preference must be built-in. +pref("app.update.cert.requireBuiltIn", true); + +// When |app.update.cert.checkAttributes| is true or not specified the +// certificate attributes specified in the |app.update.certs.| preference branch +// are checked against the certificate for the url specified by the +// |app.update.url| preference. +pref("app.update.cert.checkAttributes", true); + +// The number of certificate attribute check failures to allow for background +// update checks before notifying the user of the failure. User initiated update +// checks always notify the user of the certificate attribute check failure. +pref("app.update.cert.maxErrors", 5); + // The |app.update.certs.| preference branch contains branches that are // sequentially numbered starting at 1 that contain attribute name / value // pairs for the certificate used by the server that hosts the update xml file diff --git a/toolkit/mozapps/shared/CertUtils.jsm b/toolkit/mozapps/shared/CertUtils.jsm index 2c9c479dc5e..529c148e630 100644 --- a/toolkit/mozapps/shared/CertUtils.jsm +++ b/toolkit/mozapps/shared/CertUtils.jsm @@ -54,11 +54,14 @@ const Cu = Components.utils; * * @param aChannel * The nsIChannel that will have its certificate checked. - * @param aCerts + * @param aAllowNonBuiltInCerts (optional) + * When true certificates that aren't builtin are allowed. When false + * or not specified the certificate must be a builtin certificate. + * @param aCerts (optional) * An array of JS objects with names / values corresponding to the - * channel's expected certificate's attribute names / values. This can - * be null or an empty array. If it isn't null the the scheme for the - * channel's originalURI must be https. + * channel's expected certificate's attribute names / values. If it + * isn't null or not specified the the scheme for the channel's + * originalURI must be https. * @throws NS_ERROR_UNEXPECTED if a certificate is expected and the URI scheme * is not https. * NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the @@ -66,7 +69,7 @@ const Cu = Components.utils; * from the aCerts param is different than the expected value. * NS_ERROR_ABORT if the certificate issuer is not built-in. */ -function checkCert(aChannel, aCerts) { +function checkCert(aChannel, aAllowNonBuiltInCerts, aCerts) { if (!aChannel.originalURI.schemeIs("https")) { // Require https if there are certificate values to verify if (aCerts) { @@ -112,6 +115,8 @@ function checkCert(aChannel, aCerts) { } } + if (aAllowNonBuiltInCerts === true) + return; var issuerCert = cert; while (issuerCert.issuer && !issuerCert.issuer.equals(issuerCert)) @@ -136,6 +141,10 @@ function isBuiltinToken(tokenName) { * This class implements nsIBadCertListener. Its job is to prevent "bad cert" * security dialogs from being shown to the user. It is better to simply fail * if the certificate is bad. See bug 304286. + * + * @param aAllowNonBuiltInCerts (optional) + * When true certificates that aren't builtin are allowed. When false + * or not specified the certificate must be a builtin certificate. */ function BadCertHandler(aAllowNonBuiltInCerts) { this.allowNonBuiltInCerts = aAllowNonBuiltInCerts; diff --git a/toolkit/mozapps/shared/test/chrome/test_bug544442_checkCert.xul b/toolkit/mozapps/shared/test/chrome/test_bug544442_checkCert.xul index 34f2ea3e8df..58fa778a55b 100644 --- a/toolkit/mozapps/shared/test/chrome/test_bug544442_checkCert.xul +++ b/toolkit/mozapps/shared/test/chrome/test_bug544442_checkCert.xul @@ -58,9 +58,9 @@ function testXHRError(aEvent) { SimpleTest.finish(); } -function getCheckCertResult(aChannel, aCerts) { +function getCheckCertResult(aChannel, aAllowNonBuiltIn, aCerts) { try { - checkCert(aChannel, aCerts); + checkCert(aChannel, aAllowNonBuiltIn, aCerts); } catch (e) { return e.result; @@ -74,18 +74,22 @@ function testXHRLoad(aEvent) { var channel = aEvent.target.channel; var certs = null; - is(getCheckCertResult(channel, certs), Cr.NS_ERROR_ABORT, + is(getCheckCertResult(channel, false, certs), Cr.NS_ERROR_ABORT, "checkCert should throw NS_ERROR_ABORT when the certificate attributes " + "array passed to checkCert is null and the certificate is not builtin"); + is(getCheckCertResult(channel, true, certs), Cr.NS_OK, + "checkCert should not throw when the certificate attributes array " + + "passed to checkCert is null and builtin certificates aren't enforced"); + certs = [ { invalidAttribute: "Invalid attribute" } ]; - is(getCheckCertResult(channel, certs), Cr.NS_ERROR_ILLEGAL_VALUE, + is(getCheckCertResult(channel, false, certs), Cr.NS_ERROR_ILLEGAL_VALUE, "checkCert should throw NS_ERROR_ILLEGAL_VALUE when the certificate " + "attributes array passed to checkCert has an element that has an " + "attribute that does not exist on the certificate"); certs = [ { issuerName: "Incorrect issuerName" } ]; - is(getCheckCertResult(channel, certs), Cr.NS_ERROR_ILLEGAL_VALUE, + is(getCheckCertResult(channel, false, certs), Cr.NS_ERROR_ILLEGAL_VALUE, "checkCert should throw NS_ERROR_ILLEGAL_VALUE when the certificate " + "attributes array passed to checkCert has an element that has an " + "issuerName that is not the same as the certificate's"); @@ -95,35 +99,46 @@ function testXHRLoad(aEvent) { certs = [ { issuerName: cert.issuerName, commonName: cert.commonName } ]; - is(getCheckCertResult(channel, certs), Cr.NS_ERROR_ABORT, + is(getCheckCertResult(channel, false, certs), Cr.NS_ERROR_ABORT, "checkCert should throw NS_ERROR_ABORT when the certificate attributes " + "array passed to checkCert has a single element that has the same " + "issuerName and commonName as the certificate's and the certificate is " + "not builtin"); + is(getCheckCertResult(channel, true, certs), Cr.NS_OK, + "checkCert should not throw when the certificate attributes array " + + "passed to checkCert has a single element that has the same issuerName " + + "and commonName as the certificate's and and builtin certificates " + + "aren't enforced"); + certs = [ { issuerName: "Incorrect issuerName", invalidAttribute: "Invalid attribute" }, { issuerName: cert.issuerName, commonName: "Invalid Common Name" }, { issuerName: cert.issuerName, commonName: cert.commonName } ]; - is(getCheckCertResult(channel, certs), Cr.NS_ERROR_ABORT, + is(getCheckCertResult(channel, false, certs), Cr.NS_ERROR_ABORT, "checkCert should throw NS_ERROR_ABORT when the certificate attributes " + "array passed to checkCert has an element that has the same issuerName " + "and commonName as the certificate's and the certificate is not builtin"); + is(getCheckCertResult(channel, true, certs), Cr.NS_OK, + "checkCert should not throw when the certificate attributes array " + + "passed to checkCert has an element that has the same issuerName and " + + "commonName as the certificate's and builtin certificates aren't enforced"); + var mockChannel = { originalURI: Cc["@mozilla.org/network/io-service;1"]. getService(Ci.nsIIOService). newURI("http://example.com/", null, null) }; certs = [ ]; - is(getCheckCertResult(mockChannel, certs), Cr.NS_ERROR_UNEXPECTED, + is(getCheckCertResult(mockChannel, false, certs), Cr.NS_ERROR_UNEXPECTED, "checkCert should throw NS_ERROR_UNEXPECTED when the certificate " + "attributes array passed to checkCert is not null and the channel's " + "originalURI is not https"); certs = null; - is(getCheckCertResult(mockChannel, certs), Cr.NS_OK, + is(getCheckCertResult(mockChannel, false, certs), Cr.NS_OK, "checkCert should not throw when the certificate attributes object " + "passed to checkCert is null and the the channel's originalURI is not " + "https"); diff --git a/toolkit/mozapps/update/content/updates.js b/toolkit/mozapps/update/content/updates.js index ee11272b760..b1ae1c7860f 100644 --- a/toolkit/mozapps/update/content/updates.js +++ b/toolkit/mozapps/update/content/updates.js @@ -51,6 +51,7 @@ const CoR = Components.results; const XMLNS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; const PREF_APP_UPDATE_BILLBOARD_TEST_URL = "app.update.billboard.test_url"; +const PREF_APP_UPDATE_CERT_ERRORS = "app.update.cert.errors"; const PREF_APP_UPDATE_ENABLED = "app.update.enabled"; const PREF_APP_UPDATE_LOG = "app.update.log"; const PREF_APP_UPDATE_MANUAL_URL = "app.update.url.manual"; @@ -72,6 +73,9 @@ const STATE_FAILED = "failed"; const SRCEVT_FOREGROUND = 1; const SRCEVT_BACKGROUND = 2; +const CERT_ATTR_CHECK_FAILED_NO_UPDATE = 100; +const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101; + var gLogEnabled = false; var gUpdatesFoundPageId; @@ -399,6 +403,12 @@ var gUpdates = { // user that the background checking found an update that requires // their permission to install, and it's ready for download. this.setUpdate(arg0); + if (this.update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE || + this.update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE) { + aCallback("errorcertcheck"); + return; + } + var p = this.update.selectedPatch; if (p) { var state = p.state; @@ -654,7 +664,14 @@ var gCheckingPage = { onError: function(request, update) { LOG("gCheckingPage", "onError - proceeding to error page"); gUpdates.setUpdate(update); - gUpdates.wiz.goTo("errors"); + if (update.errorCode && + (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE || + update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE )) { + gUpdates.wiz.goTo("errorcertcheck"); + } + else { + gUpdates.wiz.goTo("errors"); + } }, /** @@ -1592,6 +1609,34 @@ var gErrorsPage = { } }; +/** + * The page shown when there is a certificate attribute check error. + */ +var gErrorCertCheckPage = { + /** + * Initialize + */ + onPageShow: function() { + gUpdates.setButtons(null, null, "okButton", true); + gUpdates.wiz.getButton("finish").focus(); + + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CERT_ERRORS)) + Services.prefs.clearUserPref(PREF_APP_UPDATE_CERT_ERRORS); + + if (gUpdates.update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE) { + document.getElementById("errorCertAttrHasUpdateLabel").hidden = false; + } + else { + document.getElementById("errorCertCheckNoUpdateLabel").hidden = false; + var manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_MANUAL_URL); + var errorLinkLabel = document.getElementById("errorCertAttrLinkLabel"); + errorLinkLabel.value = manualURL; + errorLinkLabel.setAttribute("url", manualURL); + errorLinkLabel.hidden = false; + } + } +}; + /** * The "There was an error applying a partial patch" page. */ diff --git a/toolkit/mozapps/update/content/updates.xul b/toolkit/mozapps/update/content/updates.xul index e6f9bbe2a3c..485ea87fcf1 100644 --- a/toolkit/mozapps/update/content/updates.xul +++ b/toolkit/mozapps/update/content/updates.xul @@ -229,6 +229,22 @@ + + + + + + + + + + = getPref("getIntPref", PREF_APP_UPDATE_CERT_MAXERRORS, 5)) { + var prompter = Cc["@mozilla.org/updates/update-prompt;1"]. + createInstance(Ci.nsIUpdatePrompt); + prompter.showUpdateError(update); + } + } + }; this.backgroundChecker.checkForUpdates(listener, false); }, @@ -2002,7 +2028,9 @@ Checker.prototype = { this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. createInstance(Ci.nsIXMLHttpRequest); this._request.open("GET", url, true); - this._request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(); + var allowNonBuiltIn = !getPref("getBoolPref", + PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true); + this._request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(allowNonBuiltIn); this._request.overrideMimeType("text/xml"); this._request.setRequestHeader("Cache-Control", "no-cache"); @@ -2094,6 +2122,7 @@ Checker.prototype = { var prefs = Services.prefs; var certs = null; if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL_OVERRIDE) && + getPref("getBoolPref", PREF_APP_UPDATE_CERT_CHECKATTRS, true) && prefs.getBranch(PREF_APP_UPDATE_CERTS_BRANCH).getChildList("").length) { certs = []; let counter = 1; @@ -2113,39 +2142,32 @@ Checker.prototype = { } } - var certAttrCheckFailed = false; - var status; try { - try { - gCertUtils.checkCert(this._request.channel, certs); - } - catch (e) { - Components.utils.reportError(e); - if (e.result != Cr.NS_ERROR_ILLEGAL_VALUE) - throw e; - - certAttrCheckFailed = true; - } - - // Analyze the resulting DOM and determine the set of updates. If the - // certificate attribute check failed treat it as no updates found until - // Bug 583408 is fixed. - var updates = certAttrCheckFailed ? [] : this._updates; - + // Analyze the resulting DOM and determine the set of updates. + var updates = this._updates; LOG("Checker:onLoad - number of updates available: " + updates.length); + var allowNonBuiltIn = !getPref("getBoolPref", + PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true); + gCertUtils.checkCert(this._request.channel, allowNonBuiltIn, certs); + + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CERT_ERRORS)) + Services.prefs.clearUserPref(PREF_APP_UPDATE_CERT_ERRORS); // Tell the Update Service about the updates this._callback.onCheckComplete(event.target, updates, updates.length); } catch (e) { - LOG("Checker:onLoad - there was a problem with the update service URL " + - "specified, either the XML file was malformed or it does not exist " + - "at the location specified. Exception: " + e); + LOG("Checker:onLoad - there was a problem checking for updates. " + + "Exception: " + e); var request = event.target; var status = this._getChannelStatus(request); LOG("Checker:onLoad - request.status: " + status); var update = new Update(null); update.statusText = getStatusTextFromCode(status, 404); + if (e.result == Cr.NS_ERROR_ILLEGAL_VALUE) { + update.errorCode = updates[0] ? CERT_ATTR_CHECK_FAILED_HAS_UPDATE + : CERT_ATTR_CHECK_FAILED_NO_UPDATE; + } this._callback.onError(request, update); } @@ -2803,6 +2825,14 @@ UpdatePrompt.prototype = { if (!this._enabled) return; + if (update.errorCode && + (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE || + update.errorCode != CERT_ATTR_CHECK_FAILED_HAS_UPDATE)) { + this._showUIWhenIdle(null, URI_UPDATE_PROMPT_DIALOG, null, + UPDATE_WINDOW_NAME, null, update); + return; + } + // In some cases, we want to just show a simple alert dialog: if (update.state == STATE_FAILED && update.errorCode == WRITE_ERROR) { var title = gUpdateBundle.GetStringFromName("updaterIOErrorTitle");