Bug 926219 - Relax the signature validation for locally installed apps. r=bsmith, r=fabrice
authorAntonio M. Amaya <amac@tid.es>
Tue, 19 Nov 2013 20:41:54 +0100
changeset 158274 808b91cff7796aa82b2c8d8b0f8ee402dbe90ddc
parent 158273 8197138c49692ee3e1a84858ab2ad44f457b1c62
child 158275 c24da899172a9a5bee7cdfb3e0109a695da49be0
push id25740
push userryanvm@gmail.com
push dateMon, 02 Dec 2013 21:36:28 +0000
treeherdermozilla-central@f9d8f53e8739 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmith, fabrice
bugs926219
milestone28.0a1
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
Bug 926219 - Relax the signature validation for locally installed apps. r=bsmith, r=fabrice
dom/apps/src/Webapps.jsm
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -4,16 +4,38 @@
 
 "use strict";
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
+// Possible errors thrown by the signature verifier.
+const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
+const SEC_ERROR_EXPIRED_CERTIFICATE = (SEC_ERROR_BASE + 11);
+
+// We need this to decide if we should accept or not files signed with expired
+// certificates.
+function buildIDToTime() {
+  let platformBuildID =
+    Cc["@mozilla.org/xre/app-info;1"]
+      .getService(Ci.nsIXULAppInfo).platformBuildID;
+  let platformBuildIDDate = new Date();
+  platformBuildIDDate.setUTCFullYear(platformBuildID.substr(0,4),
+                                      platformBuildID.substr(4,2) - 1,
+                                      platformBuildID.substr(6,2));
+  platformBuildIDDate.setUTCHours(platformBuildID.substr(8,2),
+                                  platformBuildID.substr(10,2),
+                                  platformBuildID.substr(12,2));
+  return platformBuildIDDate.getTime();
+}
+
+const PLATFORM_BUILD_ID_TIME = buildIDToTime();
+
 this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import('resource://gre/modules/ActivitiesService.jsm');
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import("resource://gre/modules/PermissionsInstaller.jsm");
@@ -33,16 +55,20 @@ XPCOMUtils.defineLazyGetter(this, "libcu
 #endif
 
 function debug(aMsg) {
 #ifdef MOZ_DEBUG
   dump("-*- Webapps.jsm : " + aMsg + "\n");
 #endif
 }
 
+function getNSPRErrorCode(err) {
+  return -1 * ((err) & 0xffff);
+}
+
 function supportUseCurrentProfile() {
   return Services.prefs.getBoolPref("dom.webapps.useCurrentProfile");
 }
 
 function supportSystemMessages() {
   return Services.prefs.getBoolPref("dom.sysmsg.enabled");
 }
 
@@ -2480,16 +2506,20 @@ onInstallSuccessAck: function onInstallS
 
     return Task.spawn((function*() {
       yield this._ensureSufficientStorage(aNewApp);
 
       let fullPackagePath = aManifest.fullPackagePath();
 
       // Check if it's a local file install (we've downloaded/sideloaded the
       // package already or it did exist on the build).
+      // Note that this variable also controls whether files signed with expired
+      // certificates are accepted or not. If isLocalFileInstall is true and the
+      // device date is earlier than the build generation date, then the signature
+      // will be accepted even if the certificate is expired.
       let isLocalFileInstall =
         Services.io.extractScheme(fullPackagePath) === 'file';
 
       debug("About to download " + fullPackagePath);
 
       let requestChannel = this._getRequestChannel(fullPackagePath,
                                                    isLocalFileInstall,
                                                    oldApp,
@@ -2832,17 +2862,18 @@ onInstallSuccessAck: function onInstallS
   },
 
   _openAndReadPackage: function(aZipFile, aOldApp, aNewApp, aIsLocalFileInstall,
                                 aIsUpdate, aManifest, aRequestChannel, aHash) {
     return Task.spawn((function*() {
       let zipReader, isSigned, newManifest;
 
       try {
-        [zipReader, isSigned] = yield this._openPackage(aZipFile, aOldApp);
+        [zipReader, isSigned] = yield this._openPackage(aZipFile, aOldApp,
+                                                        aIsLocalFileInstall);
         newManifest = yield this._readPackage(aOldApp, aNewApp,
                 aIsLocalFileInstall, aIsUpdate, aManifest, aRequestChannel,
                 aHash, zipReader, isSigned);
       } catch (e) {
         debug("package open/read error: " + e);
         // Something bad happened when opening/reading the package.
         // Unrecoverable error, don't bug the user.
         // Apps with installState 'pending' does not produce any
@@ -2863,41 +2894,55 @@ onInstallSuccessAck: function onInstallS
         }
       }
 
       return newManifest;
 
     }).bind(this));
   },
 
-  _openPackage: function(aZipFile, aApp) {
+  _openPackage: function(aZipFile, aApp, aIsLocalFileInstall) {
     return Task.spawn((function*() {
       let certDb;
       try {
         certDb = Cc["@mozilla.org/security/x509certdb;1"]
                    .getService(Ci.nsIX509CertDB);
       } catch (e) {
         debug("nsIX509CertDB error: " + e);
         // unrecoverable error, don't bug the user
         aApp.downloadAvailable = false;
         throw "CERTDB_ERROR";
       }
 
       let [result, zipReader] = yield this._openSignedPackage(aZipFile, certDb);
 
+      // We cannot really know if the system date is correct or
+      // not. What we can know is if it's after the build date or not,
+      // and assume the build date is correct (which we cannot
+      // really know either).
+      let isLaterThanBuildTime = Date.now() > PLATFORM_BUILD_ID_TIME;
+
       let isSigned;
 
       if (Components.isSuccessCode(result)) {
         isSigned = true;
       } else if (result == Cr.NS_ERROR_FILE_CORRUPTED) {
         throw "APP_PACKAGE_CORRUPTED";
-      } else if (result != Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED) {
+      } else if ((!aIsLocalFileInstall || isLaterThanBuildTime) &&
+                 (result != Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED)) {
         throw "INVALID_SIGNATURE";
       } else {
-        isSigned = false;
+        // If it's a localFileInstall and the validation failed
+        // because of a expired certificate, just assume it was valid
+        // and that the error occurred because the system time has not
+        // been set yet.
+        isSigned = (aIsLocalFileInstall &&
+                    (getNSPRErrorCode(result) ==
+                     SEC_ERROR_EXPIRED_CERTIFICATE));
+
         zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
                       .createInstance(Ci.nsIZipReader);
         zipReader.open(aZipFile);
       }
 
       return [zipReader, isSigned];
 
     }).bind(this));