Merge services-central into mozilla-central
authorGregory Szorc <gps@mozilla.com>
Mon, 05 Mar 2012 15:18:16 -0800
changeset 88477 7d0d1108a14e6d8024301532420d9e2442368edb
parent 88457 4b728a0908804f7cb2815115536e6236d7f2cea0 (current diff)
parent 88476 ee5e22a683245a4cfeb2b09715645399d7bbef98 (diff)
child 88478 ced3ef143d88157292e8ddb8fefdd9493bf976e6
child 88510 b8aa220cf62abb237ae780038f28de1f43b80424
child 89549 43de0f031f61558ff700874e45103dfd42270cd9
push id157
push userMs2ger@gmail.com
push dateWed, 07 Mar 2012 19:27:10 +0000
milestone13.0a1
Merge services-central into mozilla-central
--- a/browser/base/content/test/browser_aboutSyncProgress.js
+++ b/browser/base/content/test/browser_aboutSyncProgress.js
@@ -1,14 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://services-sync/main.js");
 
 let gTests = [ {
   desc: "Makes sure the progress bar appears if firstSync pref is set",
   setup: function () {
     Services.prefs.setCharPref("services.sync.firstSync", "newAccount");
   },
   run: function () {
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -750,16 +750,140 @@ let Utils = {
    * Take a base64-encoded 128-bit AES key, returning it as five groups of five
    * uppercase alphanumeric characters, separated by hyphens.
    * A.K.A. base64-to-base32 encoding.
    */
   presentEncodedKeyAsSyncKey : function presentEncodedKeyAsSyncKey(encodedKey) {
     return Utils.encodeKeyBase32(atob(encodedKey));
   },
 
+  /**
+   * Compute the HTTP MAC SHA-1 for an HTTP request.
+   *
+   * @param  identifier
+   *         (string) MAC Key Identifier.
+   * @param  key
+   *         (string) MAC Key.
+   * @param  method
+   *         (string) HTTP request method.
+   * @param  URI
+   *         (nsIURI) HTTP request URI.
+   * @param  extra
+   *         (object) Optional extra parameters. Valid keys are:
+   *           nonce_bytes - How many bytes the nonce should be. This defaults
+   *             to 8. Note that this many bytes are Base64 encoded, so the
+   *             string length of the nonce will be longer than this value.
+   *           ts - Timestamp to use. Should only be defined for testing.
+   *           nonce - String nonce. Should only be defined for testing as this
+   *             function will generate a cryptographically secure random one
+   *             if not defined.
+   *           ext - Extra string to be included in MAC. Per the HTTP MAC spec,
+   *             the format is undefined and thus application specific.
+   * @returns
+   *         (object) Contains results of operation and input arguments (for
+   *           symmetry). The object has the following keys:
+   *
+   *           identifier - (string) MAC Key Identifier (from arguments).
+   *           key - (string) MAC Key (from arguments).
+   *           method - (string) HTTP request method (from arguments).
+   *           hostname - (string) HTTP hostname used (derived from arguments).
+   *           port - (string) HTTP port number used (derived from arguments).
+   *           mac - (string) Raw HMAC digest bytes.
+   *           getHeader - (function) Call to obtain the string Authorization
+   *             header value for this invocation.
+   *           nonce - (string) Nonce value used.
+   *           ts - (number) Integer seconds since Unix epoch that was used.
+   */
+  computeHTTPMACSHA1: function computeHTTPMACSHA1(identifier, key, method,
+                                                  uri, extra) {
+    let ts = (extra && extra.ts) ? extra.ts : Math.floor(Date.now() / 1000);
+    let nonce_bytes = (extra && extra.nonce_bytes > 0) ? extra.nonce_bytes : 8;
+
+    // We are allowed to use more than the Base64 alphabet if we want.
+    let nonce = (extra && extra.nonce)
+                ? extra.nonce
+                : btoa(Utils.generateRandomBytes(nonce_bytes));
+
+    let host = uri.asciiHost;
+    let port;
+    let usedMethod = method.toUpperCase();
+
+    if (uri.port != -1) {
+      port = uri.port;
+    } else if (uri.scheme == "http") {
+      port = "80";
+    } else if (uri.scheme == "https") {
+      port = "443";
+    } else {
+      throw new Error("Unsupported URI scheme: " + uri.scheme);
+    }
+
+    let ext = (extra && extra.ext) ? extra.ext : "";
+
+    let requestString = ts.toString(10) + "\n" +
+                        nonce           + "\n" +
+                        usedMethod      + "\n" +
+                        uri.path        + "\n" +
+                        host            + "\n" +
+                        port            + "\n" +
+                        ext             + "\n";
+
+    let hasher = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1,
+                                      Utils.makeHMACKey(key));
+    let mac = Utils.digestBytes(requestString, hasher);
+
+    function getHeader() {
+      return Utils.getHTTPMACSHA1Header(this.identifier, this.ts, this.nonce,
+                                        this.mac, this.ext);
+    }
+
+    return {
+      identifier: identifier,
+      key:        key,
+      method:     usedMethod,
+      hostname:   host,
+      port:       port,
+      mac:        mac,
+      nonce:      nonce,
+      ts:         ts,
+      ext:        ext,
+      getHeader:  getHeader
+    };
+  },
+
+  /**
+   * Obtain the HTTP MAC Authorization header value from fields.
+   *
+   * @param  identifier
+   *         (string) MAC key identifier.
+   * @param  ts
+   *         (number) Integer seconds since Unix epoch.
+   * @param  nonce
+   *         (string) Nonce value.
+   * @param  mac
+   *         (string) Computed HMAC digest (raw bytes).
+   * @param  ext
+   *         (optional) (string) Extra string content.
+   * @returns
+   *         (string) Value to put in Authorization header.
+   */
+  getHTTPMACSHA1Header: function getHTTPMACSHA1Header(identifier, ts, nonce,
+                                                      mac, ext) {
+    let header ='MAC id="' + identifier + '", ' +
+                'ts="'     + ts         + '", ' +
+                'nonce="'  + nonce      + '", ' +
+                'mac="'    + btoa(mac)  + '"';
+
+    if (!ext) {
+      return header;
+    }
+
+    return header += ', ext="' + ext +'"';
+  },
+
   makeURI: function Weave_makeURI(URIString) {
     if (!URIString)
       return null;
     try {
       return Services.io.newURI(URIString, null, null);
     } catch (e) {
       let log = Log4Moz.repository.getLogger("Sync.Utils");
       log.debug("Could not create URI: " + Utils.exceptionStr(e));
--- a/services/sync/tests/unit/head_helpers.js
+++ b/services/sync/tests/unit/head_helpers.js
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://services-sync/async.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/engines.js");
-var btoa;
+let btoa;
+let atob;
 
 let provider = {
   getFile: function(prop, persistent) {
     persistent.value = true;
     switch (prop) {
       case "ExtPrefDL":
         return [Services.dirsvc.get("CurProcD", Ci.nsIFile)];
       default:
@@ -34,16 +35,17 @@ function waitForZeroTimer(callback) {
       return;
     }
     callback();
   }
   timer = Utils.namedTimer(wait, 150, {}, "timer");
 }
 
 btoa = Cu.import("resource://services-sync/log4moz.js").btoa;
+atob = Cu.import("resource://services-sync/log4moz.js").atob;
 function getTestLogger(component) {
   return Log4Moz.repository.getLogger("Testing");
 }
 
 function initTestLogging(level) {
   function LogStats() {
     this.errorsLogged = 0;
   }
--- a/services/sync/tests/unit/test_load_modules.js
+++ b/services/sync/tests/unit/test_load_modules.js
@@ -1,27 +1,34 @@
 const modules = [
+                 "addonsreconciler.js",
+                 "async.js",
                  "constants.js",
+                 "engines/addons.js",
                  "engines/bookmarks.js",
                  "engines/clients.js",
                  "engines/forms.js",
                  "engines/history.js",
                  "engines/passwords.js",
                  "engines/prefs.js",
                  "engines/tabs.js",
                  "engines.js",
                  "ext/Observers.js",
                  "ext/Preferences.js",
                  "identity.js",
+                 "jpakeclient.js",
                  "log4moz.js",
                  "main.js",
                  "notifications.js",
+                 "policies.js",
                  "record.js",
                  "resource.js",
+                 "rest.js",
                  "service.js",
+                 "status.js",
                  "util.js",
 ];
 
 function run_test() {
   for each (let m in modules) {
     _("Attempting to load resource://services-sync/" + m);
     Cu.import("resource://services-sync/" + m, {});
   }
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/unit/test_utils_httpmac.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://services-sync/util.js");
+
+function run_test() {
+  initTestLogging();
+
+  run_next_test();
+}
+
+add_test(function test_sha1() {
+  _("Ensure HTTP MAC SHA1 generation works as expected.");
+
+  let id = "vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7";
+  let key = "b8u1cc5iiio5o319og7hh8faf2gi5ym4aq0zwf112cv1287an65fudu5zj7zo7dz";
+  let ts = 1329181221;
+  let method = "GET";
+  let nonce = "wGX71";
+  let uri = Utils.makeURI("http://10.250.2.176/alias/");
+
+  let result = Utils.computeHTTPMACSHA1(id, key, method, uri, {ts: ts,
+                                                               nonce: nonce});
+
+  do_check_eq(btoa(result.mac), "jzh5chjQc2zFEvLbyHnPdX11Yck=");
+
+  do_check_eq(result.getHeader(),
+              'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
+              'ts="1329181221", nonce="wGX71", mac="jzh5chjQc2zFEvLbyHnPdX11Yck="');
+
+  let ext = "EXTRA DATA; foo,bar=1";
+
+  let result = Utils.computeHTTPMACSHA1(id, key, method, uri, {ts: ts,
+                                                               nonce: nonce,
+                                                               ext: ext});
+  do_check_eq(btoa(result.mac), "bNf4Fnt5k6DnhmyipLPkuZroH68=");
+  do_check_eq(result.getHeader(),
+              'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
+              'ts="1329181221", nonce="wGX71", mac="bNf4Fnt5k6DnhmyipLPkuZroH68=", ' +
+              'ext="EXTRA DATA; foo,bar=1"');
+
+  run_next_test();
+});
+
+add_test(function test_nonce_length() {
+  _("Ensure custom nonce lengths are honoured.");
+
+  function get_mac(length) {
+    let uri = Utils.makeURI("http://example.com/");
+    return Utils.computeHTTPMACSHA1("foo", "bar", "GET", uri, {
+      nonce_bytes: length
+    });
+  }
+
+  let result = get_mac(12);
+  do_check_eq(12, atob(result.nonce).length);
+
+  let result = get_mac(2);
+  do_check_eq(2, atob(result.nonce).length);
+
+  let result = get_mac(0);
+  do_check_eq(8, atob(result.nonce).length);
+
+  let result = get_mac(-1);
+  do_check_eq(8, atob(result.nonce).length);
+
+  run_next_test();
+});
--- a/services/sync/tests/unit/xpcshell.ini
+++ b/services/sync/tests/unit/xpcshell.ini
@@ -1,11 +1,13 @@
 [DEFAULT]
 head = head_appinfo.js head_helpers.js head_http_server.js
-tail = 
+tail =
+
+[test_load_modules.js]
 
 [test_Observers.js]
 [test_Preferences.js]
 [test_addons_engine.js]
 [test_addons_reconciler.js]
 [test_addons_store.js]
 [test_addons_tracker.js]
 [test_async_chain.js]
@@ -44,17 +46,16 @@ skip-if = os == "android"
 [test_hmac_error.js]
 [test_httpd_sync_server.js]
 [test_interval_triggers.js]
 [test_jpakeclient.js]
 # Bug 618233: this test produces random failures on Windows 7.
 # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini)
 skip-if = os == "win" || os == "android"
 [test_keys.js]
-[test_load_modules.js]
 [test_log4moz.js]
 [test_node_reassignment.js]
 [test_notifications.js]
 [test_password_store.js]
 [test_password_tracker.js]
 [test_places_guid_downgrade.js]
 [test_prefs_store.js]
 [test_prefs_tracker.js]
@@ -110,16 +111,17 @@ skip-if = os == "android"
 [test_utils_deepEquals.js]
 [test_utils_deferGetSet.js]
 [test_utils_deriveKey.js]
 [test_utils_encodeBase32.js]
 [test_utils_ensureOneOpen.js]
 [test_utils_getErrorString.js]
 [test_utils_getIcon.js]
 [test_utils_hkdfExpand.js]
+[test_utils_httpmac.js]
 [test_utils_json.js]
 [test_utils_lazyStrings.js]
 [test_utils_lock.js]
 [test_utils_makeGUID.js]
 [test_utils_makeURI.js]
 [test_utils_namedTimer.js]
 [test_utils_notify.js]
 [test_utils_passphrase.js]