App update patch for Bug 544442 - Add support for signed AUS update snippets. r=dtownsend, a=blocking2.0-Beta3
authorRobert Strong <robert.bugzilla@gmail.com>
Sun, 01 Aug 2010 19:02:21 -0700
changeset 48687 14bbdcaf695f89121c81d7475ffd73c3a4180013
parent 48686 c262b545756f46d468d351725c6628ee00f369a9
child 48688 488ea306526be3c11c343f097b2990d3061f3365
push id14766
push userrstrong@mozilla.com
push dateMon, 02 Aug 2010 02:03:48 +0000
treeherdermozilla-central@14bbdcaf695f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdtownsend, blocking2.0-Beta3
bugs544442
milestone2.0b3pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
App update patch for Bug 544442 - Add support for signed AUS update snippets. r=dtownsend, a=blocking2.0-Beta3
browser/app/profile/firefox.js
toolkit/mozapps/update/nsUpdateService.js
toolkit/mozapps/update/test/chrome/Makefile.in
toolkit/mozapps/update/test/chrome/test_0120_cert_valid_attributes_not_builtin.xul
toolkit/mozapps/update/test/chrome/test_0121_cert_invalid_attribute_name.xul
toolkit/mozapps/update/test/shared.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -80,32 +80,50 @@ pref("browser.dictionaries.download.url"
 //           default=10 minutes
 pref("app.update.timer", 600000);
 
 // App-specific update preferences
 
 // The interval to check for updates (app.update.interval) is defined in
 // firefox-branding.js
 
+// Enables some extra Application Update Logging (can reduce performance)
+pref("app.update.log", false);
+
+// 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
+// as specified in the |app.update.url| preference. When these preferences are
+// present the following conditions apply for a successful update check:
+// 1. the uri scheme must be https
+// 2. the preference name must exist as an attribute name on the certificate and
+//    the value for the name must be the same as the value for the attribute name
+//    on the certificate.
+// If these conditions aren't met it will be treated the same as when there is
+// no update available. This validation will not be performed when using the
+// |app.update.url.override| preference for update checking.
+pref("app.update.certs.1.issuerName", "OU=Equifax Secure Certificate Authority,O=Equifax,C=US");
+pref("app.update.certs.1.commonName", "*.mozilla.org");
+
 // Whether or not app updates are enabled
 pref("app.update.enabled", true);
 
 // This preference turns on app.update.mode and allows automatic download and
 // install to take place. We use a separate boolean toggle for this to make
 // the UI easier to construct.
 pref("app.update.auto", true);
 
 // Defines how the Application Update Service notifies the user about updates:
 //
 // AUM Set to:        Minor Releases:     Major Releases:
 // 0                  download no prompt  download no prompt
 // 1                  download no prompt  download no prompt if no incompatibilities
 // 2                  download no prompt  prompt
 //
-// See chart in nsUpdateService.js.in for more details
+// See chart in nsUpdateService.js source for more details
 //
 pref("app.update.mode", 1);
 
 // If set to true, the Update Service will present no UI for any event.
 pref("app.update.silent", false);
 
 // Update service URL:
 pref("app.update.url", "https://aus2.mozilla.org/update/3/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -47,16 +47,17 @@ Components.utils.import("resource://gre/
 Components.utils.import("resource://gre/modules/AddonManager.jsm");
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 const PREF_APP_UPDATE_AUTO                = "app.update.auto";
 const PREF_APP_UPDATE_BACKGROUND_INTERVAL = "app.update.download.backgroundInterval";
+const PREF_APP_UPDATE_CERTS_BRANCH        = "app.update.certs.";
 const PREF_APP_UPDATE_CHANNEL             = "app.update.channel";
 const PREF_APP_UPDATE_ENABLED             = "app.update.enabled";
 const PREF_APP_UPDATE_IDLETIME            = "app.update.idletime";
 const PREF_APP_UPDATE_INCOMPATIBLE_MODE   = "app.update.incompatible.mode";
 const PREF_APP_UPDATE_INTERVAL            = "app.update.interval";
 const PREF_APP_UPDATE_LOG                 = "app.update.log";
 const PREF_APP_UPDATE_MODE                = "app.update.mode";
 const PREF_APP_UPDATE_NEVER_BRANCH        = "app.update.never.";
@@ -2046,27 +2047,29 @@ Checker.prototype = {
    */
   onProgress: function UC_onProgress(event) {
     LOG("Checker:onProgress - " + event.position + "/" + event.totalSize);
     this._callback.onProgress(event.target, event.position, event.totalSize);
   },
 
   /**
    * Returns an array of nsIUpdate objects discovered by the update check.
+   * @throws if the XML document element node name is not updates.
    */
   get _updates() {
     var updatesElement = this._request.responseXML.documentElement;
     if (!updatesElement) {
       LOG("Checker:_updates get - empty updates document?!");
       return [];
     }
 
     if (updatesElement.nodeName != "updates") {
       LOG("Checker:updates get - unexpected node name!");
-      throw "";
+      throw new Error("Unexpected node name, expected: updates, got: " +
+                      updatesElement.nodeName);
     }
 
     const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
     var updates = [];
     for (var i = 0; i < updatesElement.childNodes.length; ++i) {
       var updateElement = updatesElement.childNodes.item(i);
       if (updateElement.nodeType != ELEMENT_NODE ||
           updateElement.localName != "update")
@@ -2106,24 +2109,59 @@ Checker.prototype = {
   /**
    * The XMLHttpRequest succeeded and the document was loaded.
    * @param   event
    *          The nsIDOMEvent for the load
    */
   onLoad: function UC_onLoad(event) {
     LOG("Checker:onLoad - request completed downloading document");
 
+    var certs = null;
+    if (!gPref.prefHasUserValue(PREF_APP_UPDATE_URL_OVERRIDE) &&
+        gPref.getBranch(PREF_APP_UPDATE_CERTS_BRANCH).getChildList("").length) {
+      certs = [];
+      let counter = 1;
+      while (true) {
+        let prefBranchCert = gPref.getBranch(PREF_APP_UPDATE_CERTS_BRANCH +
+                                             counter + ".");
+        let prefCertAttrs = prefBranchCert.getChildList("");
+        if (prefCertAttrs.length == 0)
+          break;
+
+        let certAttrs = {};
+        for each (let prefCertAttr in prefCertAttrs)
+          certAttrs[prefCertAttr] = prefBranchCert.getCharPref(prefCertAttr);
+
+        certs.push(certAttrs);
+        counter++;
+      }
+    }
+
+    var certAttrCheckFailed = false;
+    var status;
     try {
-      gCertUtils.checkCert(this._request.channel);
-      // Analyze the resulting DOM and determine the set of updates to install
-      var updates = this._updates;
+      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;
 
       LOG("Checker:onLoad - number of updates available: " + updates.length);
 
-      // ... and tell the Update Service about what we discovered.
+      // 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);
       var request = event.target;
       var status = this._getChannelStatus(request);
--- a/toolkit/mozapps/update/test/chrome/Makefile.in
+++ b/toolkit/mozapps/update/test/chrome/Makefile.in
@@ -79,16 +79,18 @@ include $(DEPTH)/config/autoconf.mk
   test_0081_error_patchApplyFailure_partial_only.xul \
   test_0082_error_patchApplyFailure_complete_only.xul \
   test_0083_error_patchApplyFailure_partial_complete.xul \
   test_0084_error_patchApplyFailure_verify_failed.xul \
   test_0091_installed.xul \
   test_0092_finishedBackground.xul \
   test_0111_neverButton_basic.xul \
   test_0112_neverButton_billboard.xul \
+  test_0120_cert_valid_attributes_not_builtin.xul \
+  test_0121_cert_invalid_attribute_name.xul \
   test_9999_cleanup.xul \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
 libs:: $(_OTHER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
 
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/chrome/test_0120_cert_valid_attributes_not_builtin.xul
@@ -0,0 +1,113 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Test update checking wizard with valid certificate attribute names and values and cert not built-in"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="runTestDefault();">
+<script type="application/javascript" 
+        src="chrome://mochikit/content/MochiKit/packed.js"/>
+<script type="application/javascript"
+        src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+        src="chrome://mochikit/content/chrome/toolkit/mozapps/update/test/chrome/utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+  pageid: PAGEID_CHECKING
+}, {
+  pageid: PAGEID_ERRORS,
+  buttonClick: "finish"
+} ];
+
+Components.utils.import("resource://gre/modules/CertUtils.jsm");
+
+const CERT_ATTRS = ["nickname", "emailAddress", "subjectName", "commonName",
+                    "organization", "organizationalUnit", "sha1Fingerprint",
+                    "md5Fingerprint", "tokenName", "issuerName", "serialNumber",
+                    "issuerCommonName", "issuerOrganization",
+                    "issuerOrganizationUnit", "dbKey", "windowTitle"];
+var gDefaultAppUpdateURL;
+
+function runTest() {
+  debugDump("Entering runTest");
+
+  var request = AUS_Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+                createInstance(AUS_Ci.nsIXMLHttpRequest);
+  request.open("GET", "https://example.com/", true);
+  request.channel.notificationCallbacks = new BadCertHandler(true);
+  request.onload = function(event) { testXHRLoad(event); };
+  request.onerror = function(event) { testXHRError(event); };
+  request.send(null);
+}
+
+function testXHRError(aEvent) {
+  debugDump("Entering testXHRError");
+
+  ok(true, "Entering testXHRError - something went wrong");
+
+  var request = aEvent.target;
+  var status = 0;
+  try {
+    status = request.status;
+  }
+  catch (e) {
+  }
+
+  if (status == 0)
+    status = request.channel.QueryInterface(AUS_Ci.nsIRequest).status;
+
+  ok(false, "XHR onerror called: " + status);
+
+  finishTestDefault();
+}
+
+function testXHRLoad(aEvent) {
+  debugDump("Entering testXHRLoad");
+
+  var channel = aEvent.target.channel;
+  var cert = channel.securityInfo.QueryInterface(AUS_Ci.nsISSLStatusProvider).
+             SSLStatus.QueryInterface(AUS_Ci.nsISSLStatus).serverCert;
+  CERT_ATTRS.forEach(function(aCertAttrName) {
+    Services.prefs.setCharPref(PREF_APP_UPDATE_CERTS_BRANCH + "1." +
+                               aCertAttrName, cert[aCertAttrName]);
+  });
+
+  var queryString = "?showDetails=1" + getVersionParams();
+  var url = "https://example.com/" + URL_PATH + "/update.sjs?showDetails=1" +
+            getVersionParams();
+  gDefaultAppUpdateURL = gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_URL);
+  gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_URL, url);
+  debugDump("Default Update URL: " + url);
+
+  gUP.checkForUpdates();
+}
+
+function finishTest() {
+  debugDump("Entering finishTest");
+
+  CERT_ATTRS.forEach(function(aCertAttrName) {
+    Services.prefs.clearUserPref(PREF_APP_UPDATE_CERTS_BRANCH + "1." +
+                                 aCertAttrName);
+  });
+  gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_URL, gDefaultAppUpdateURL);
+  finishTestDefault();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test"></pre>
+</body>
+</window>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/chrome/test_0121_cert_invalid_attribute_name.xul
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Test update checking wizard with invalid certificate attribute name"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="runTestDefault();">
+<script type="application/javascript" 
+        src="chrome://mochikit/content/MochiKit/packed.js"/>
+<script type="application/javascript"
+        src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+        src="chrome://mochikit/content/chrome/toolkit/mozapps/update/test/chrome/utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+// This is treated as no updates found until Bug 583408 is fixed
+const TESTS = [ {
+  pageid: PAGEID_CHECKING
+}, {
+  pageid: PAGEID_NO_UPDATES_FOUND,
+  buttonClick: "finish"
+} ];
+
+const PREF_APP_UPDATE_CERT_INVALID_ATTR_NAME = PREF_APP_UPDATE_CERTS_BRANCH +
+                                               "1.invalidName";
+var gDefaultAppUpdateURL;
+
+function runTest() {
+  debugDump("Entering runTest");
+
+  Services.prefs.setCharPref(PREF_APP_UPDATE_CERT_INVALID_ATTR_NAME,
+                             "Invalid Attribute Name");
+
+  var queryString = "?showDetails=1" + getVersionParams();
+  var url = "https://example.com/" + URL_PATH + "/update.sjs?showDetails=1" +
+            getVersionParams();
+  gDefaultAppUpdateURL = gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_URL);
+  gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_URL, url);
+  debugDump("Default Update URL: " + url);
+
+  gUP.checkForUpdates();
+}
+
+function finishTest() {
+  debugDump("Entering finishTest - clearing user preference " +
+            PREF_APP_UPDATE_CERT_INVALID_ATTR_NAME);
+  Services.prefs.clearUserPref(PREF_APP_UPDATE_CERT_INVALID_ATTR_NAME);
+  gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_URL, gDefaultAppUpdateURL);
+  finishTestDefault();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test"></pre>
+</body>
+</window>
--- a/toolkit/mozapps/update/test/shared.js
+++ b/toolkit/mozapps/update/test/shared.js
@@ -39,17 +39,17 @@
 
 // const Cc, Ci, and Cr are defined in netwerk/test/httpserver/httpd.js so we
 // need to define unique ones.
 const AUS_Cc = Components.classes;
 const AUS_Ci = Components.interfaces;
 const AUS_Cr = Components.results;
 const AUS_Cu = Components.utils;
 
-const PREF_APP_UPDATE_CERT_ATTR_BRANCH  = "app.update.cert.attributes.";
+const PREF_APP_UPDATE_CERTS_BRANCH      = "app.update.certs.";
 const PREF_APP_UPDATE_CHANNEL           = "app.update.channel";
 const PREF_APP_UPDATE_ENABLED           = "app.update.enabled";
 const PREF_APP_UPDATE_IDLETIME          = "app.update.idletime";
 const PREF_APP_UPDATE_LOG               = "app.update.log";
 const PREF_APP_UPDATE_SHOW_INSTALLED_UI = "app.update.showInstalledUI";
 const PREF_APP_UPDATE_URL               = "app.update.url";
 const PREF_APP_UPDATE_URL_DETAILS       = "app.update.url.details";
 const PREF_APP_UPDATE_URL_OVERRIDE      = "app.update.url.override";