services/sync/tests/unit/test_keys.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* Any copyright is dedicated to the Public Domain.
     2  * http://creativecommons.org/publicdomain/zero/1.0/ */
     4 Cu.import("resource://services-sync/constants.js");
     5 Cu.import("resource://services-sync/identity.js");
     6 Cu.import("resource://services-sync/keys.js");
     7 Cu.import("resource://services-sync/record.js");
     8 Cu.import("resource://services-sync/util.js");
    10 let collectionKeys = new CollectionKeyManager();
    12 function sha256HMAC(message, key) {
    13   let h = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key);
    14   return Utils.digestBytes(message, h);
    15 }
    17 function do_check_array_eq(a1, a2) {
    18   do_check_eq(a1.length, a2.length);
    19   for (let i = 0; i < a1.length; ++i) {
    20     do_check_eq(a1[i], a2[i]);
    21   }
    22 }
    24 function do_check_keypair_eq(a, b) {
    25   do_check_eq(2, a.length);
    26   do_check_eq(2, b.length);
    27   do_check_eq(a[0], b[0]);
    28   do_check_eq(a[1], b[1]);
    29 }
    31 function test_time_keyFromString(iterations) {
    32   let k;
    33   let o;
    34   let b = new BulkKeyBundle("dummy");
    35   let d = Utils.decodeKeyBase32("ababcdefabcdefabcdefabcdef");
    36   b.generateRandom();
    38   _("Running " + iterations + " iterations of hmacKeyObject + sha256HMAC.");
    39   for (let i = 0; i < iterations; ++i) {
    40     let k = b.hmacKeyObject;
    41     o = sha256HMAC(d, k);
    42   }
    43   do_check_true(!!o);
    44   _("Done.");
    45 }
    47 add_test(function test_set_invalid_values() {
    48   _("Ensure that setting invalid encryption and HMAC key values is caught.");
    50   let bundle = new BulkKeyBundle("foo");
    52   let thrown = false;
    53   try {
    54     bundle.encryptionKey = null;
    55   } catch (ex) {
    56     thrown = true;
    57     do_check_eq(ex.message.indexOf("Encryption key can only be set to"), 0);
    58   } finally {
    59     do_check_true(thrown);
    60     thrown = false;
    61   }
    63   try {
    64     bundle.encryptionKey = ["trollololol"];
    65   } catch (ex) {
    66     thrown = true;
    67     do_check_eq(ex.message.indexOf("Encryption key can only be set to"), 0);
    68   } finally {
    69     do_check_true(thrown);
    70     thrown = false;
    71   }
    73   try {
    74     bundle.hmacKey = Utils.generateRandomBytes(15);
    75   } catch (ex) {
    76     thrown = true;
    77     do_check_eq(ex.message.indexOf("HMAC key must be at least 128"), 0);
    78   } finally {
    79     do_check_true(thrown);
    80     thrown = false;
    81   }
    83   try {
    84     bundle.hmacKey = null;
    85   } catch (ex) {
    86     thrown = true;
    87     do_check_eq(ex.message.indexOf("HMAC key can only be set to string"), 0);
    88   } finally {
    89     do_check_true(thrown);
    90     thrown = false;
    91   }
    93   try {
    94     bundle.hmacKey = ["trollolol"];
    95   } catch (ex) {
    96     thrown = true;
    97     do_check_eq(ex.message.indexOf("HMAC key can only be set to"), 0);
    98   } finally {
    99     do_check_true(thrown);
   100     thrown = false;
   101   }
   103   try {
   104     bundle.hmacKey = Utils.generateRandomBytes(15);
   105   } catch (ex) {
   106     thrown = true;
   107     do_check_eq(ex.message.indexOf("HMAC key must be at least 128"), 0);
   108   } finally {
   109     do_check_true(thrown);
   110     thrown = false;
   111   }
   113   run_next_test();
   114 });
   116 add_test(function test_repeated_hmac() {
   117   let testKey = "ababcdefabcdefabcdefabcdef";
   118   let k = Utils.makeHMACKey("foo");
   119   let one = sha256HMAC(Utils.decodeKeyBase32(testKey), k);
   120   let two = sha256HMAC(Utils.decodeKeyBase32(testKey), k);
   121   do_check_eq(one, two);
   123   run_next_test();
   124 });
   126 add_test(function test_sync_key_bundle_derivation() {
   127   _("Ensure derivation from known values works.");
   129   // The known values in this test were originally verified against Firefox
   130   // Home.
   131   let bundle = new SyncKeyBundle("st3fan", "q7ynpwq7vsc9m34hankbyi3s3i");
   133   // These should be compared to the results from Home, as they once were.
   134   let e = "14b8c09fa84e92729ee695160af6e0385f8f6215a25d14906e1747bdaa2de426";
   135   let h = "370e3566245d79fe602a3adb5137e42439cd2a571235197e0469d7d541b07875";
   137   let realE = Utils.bytesAsHex(bundle.encryptionKey);
   138   let realH = Utils.bytesAsHex(bundle.hmacKey);
   140   _("Real E: " + realE);
   141   _("Real H: " + realH);
   142   do_check_eq(realH, h);
   143   do_check_eq(realE, e);
   145   run_next_test();
   146 });
   148 add_test(function test_keymanager() {
   149   let testKey = "ababcdefabcdefabcdefabcdef";
   150   let username = "john@example.com";
   152   // Decode the key here to mirror what generateEntry will do,
   153   // but pass it encoded into the KeyBundle call below.
   155   let sha256inputE = "" + HMAC_INPUT + username + "\x01";
   156   let key = Utils.makeHMACKey(Utils.decodeKeyBase32(testKey));
   157   let encryptKey = sha256HMAC(sha256inputE, key);
   159   let sha256inputH = encryptKey + HMAC_INPUT + username + "\x02";
   160   let hmacKey = sha256HMAC(sha256inputH, key);
   162   // Encryption key is stored in base64 for WeaveCrypto convenience.
   163   do_check_eq(encryptKey, new SyncKeyBundle(username, testKey).encryptionKey);
   164   do_check_eq(hmacKey,    new SyncKeyBundle(username, testKey).hmacKey);
   166   // Test with the same KeyBundle for both.
   167   let obj = new SyncKeyBundle(username, testKey);
   168   do_check_eq(hmacKey, obj.hmacKey);
   169   do_check_eq(encryptKey, obj.encryptionKey);
   171   run_next_test();
   172 });
   174 add_test(function test_collections_manager() {
   175   let log = Log.repository.getLogger("Test");
   176   Log.repository.rootLogger.addAppender(new Log.DumpAppender());
   178   let identity = new IdentityManager();
   180   identity.account = "john@example.com";
   181   identity.syncKey = "a-bbbbb-ccccc-ddddd-eeeee-fffff";
   183   let keyBundle = identity.syncKeyBundle;
   185   /*
   186    * Build a test version of storage/crypto/keys.
   187    * Encrypt it with the sync key.
   188    * Pass it into the CollectionKeyManager.
   189    */
   191   log.info("Building storage keys...");
   192   let storage_keys = new CryptoWrapper("crypto", "keys");
   193   let default_key64 = Svc.Crypto.generateRandomKey();
   194   let default_hmac64 = Svc.Crypto.generateRandomKey();
   195   let bookmarks_key64 = Svc.Crypto.generateRandomKey();
   196   let bookmarks_hmac64 = Svc.Crypto.generateRandomKey();
   198   storage_keys.cleartext = {
   199     "default": [default_key64, default_hmac64],
   200     "collections": {"bookmarks": [bookmarks_key64, bookmarks_hmac64]},
   201   };
   202   storage_keys.modified = Date.now()/1000;
   203   storage_keys.id = "keys";
   205   log.info("Encrypting storage keys...");
   207   // Use passphrase (sync key) itself to encrypt the key bundle.
   208   storage_keys.encrypt(keyBundle);
   210   // Sanity checking.
   211   do_check_true(null == storage_keys.cleartext);
   212   do_check_true(null != storage_keys.ciphertext);
   214   log.info("Updating collection keys.");
   216   // updateContents decrypts the object, releasing the payload for us to use.
   217   // Returns true, because the default key has changed.
   218   do_check_true(collectionKeys.updateContents(keyBundle, storage_keys));
   219   let payload = storage_keys.cleartext;
   221   _("CK: " + JSON.stringify(collectionKeys._collections));
   223   // Test that the CollectionKeyManager returns a similar WBO.
   224   let wbo = collectionKeys.asWBO("crypto", "keys");
   226   _("WBO: " + JSON.stringify(wbo));
   227   _("WBO cleartext: " + JSON.stringify(wbo.cleartext));
   229   // Check the individual contents.
   230   do_check_eq(wbo.collection, "crypto");
   231   do_check_eq(wbo.id, "keys");
   232   do_check_eq(undefined, wbo.modified);
   233   do_check_eq(collectionKeys.lastModified, storage_keys.modified);
   234   do_check_true(!!wbo.cleartext.default);
   235   do_check_keypair_eq(payload.default, wbo.cleartext.default);
   236   do_check_keypair_eq(payload.collections.bookmarks, wbo.cleartext.collections.bookmarks);
   238   do_check_true('bookmarks' in collectionKeys._collections);
   239   do_check_false('tabs' in collectionKeys._collections);
   241   _("Updating contents twice with the same data doesn't proceed.");
   242   storage_keys.encrypt(keyBundle);
   243   do_check_false(collectionKeys.updateContents(keyBundle, storage_keys));
   245   /*
   246    * Test that we get the right keys out when we ask for
   247    * a collection's tokens.
   248    */
   249   let b1 = new BulkKeyBundle("bookmarks");
   250   b1.keyPairB64 = [bookmarks_key64, bookmarks_hmac64];
   251   let b2 = collectionKeys.keyForCollection("bookmarks");
   252   do_check_keypair_eq(b1.keyPair, b2.keyPair);
   254   // Check key equality.
   255   do_check_true(b1.equals(b2));
   256   do_check_true(b2.equals(b1));
   258   b1 = new BulkKeyBundle("[default]");
   259   b1.keyPairB64 = [default_key64, default_hmac64];
   261   do_check_false(b1.equals(b2));
   262   do_check_false(b2.equals(b1));
   264   b2 = collectionKeys.keyForCollection(null);
   265   do_check_keypair_eq(b1.keyPair, b2.keyPair);
   267   /*
   268    * Checking for update times.
   269    */
   270   let info_collections = {};
   271   do_check_true(collectionKeys.updateNeeded(info_collections));
   272   info_collections["crypto"] = 5000;
   273   do_check_false(collectionKeys.updateNeeded(info_collections));
   274   info_collections["crypto"] = 1 + (Date.now()/1000);              // Add one in case computers are fast!
   275   do_check_true(collectionKeys.updateNeeded(info_collections));
   277   collectionKeys.lastModified = null;
   278   do_check_true(collectionKeys.updateNeeded({}));
   280   /*
   281    * Check _compareKeyBundleCollections.
   282    */
   283   function newBundle(name) {
   284     let r = new BulkKeyBundle(name);
   285     r.generateRandom();
   286     return r;
   287   }
   288   let k1 = newBundle("k1");
   289   let k2 = newBundle("k2");
   290   let k3 = newBundle("k3");
   291   let k4 = newBundle("k4");
   292   let k5 = newBundle("k5");
   293   let coll1 = {"foo": k1, "bar": k2};
   294   let coll2 = {"foo": k1, "bar": k2};
   295   let coll3 = {"foo": k1, "bar": k3};
   296   let coll4 = {"foo": k4};
   297   let coll5 = {"baz": k5, "bar": k2};
   298   let coll6 = {};
   300   let d1 = collectionKeys._compareKeyBundleCollections(coll1, coll2); // []
   301   let d2 = collectionKeys._compareKeyBundleCollections(coll1, coll3); // ["bar"]
   302   let d3 = collectionKeys._compareKeyBundleCollections(coll3, coll2); // ["bar"]
   303   let d4 = collectionKeys._compareKeyBundleCollections(coll1, coll4); // ["bar", "foo"]
   304   let d5 = collectionKeys._compareKeyBundleCollections(coll5, coll2); // ["baz", "foo"]
   305   let d6 = collectionKeys._compareKeyBundleCollections(coll6, coll1); // ["bar", "foo"]
   306   let d7 = collectionKeys._compareKeyBundleCollections(coll5, coll5); // []
   307   let d8 = collectionKeys._compareKeyBundleCollections(coll6, coll6); // []
   309   do_check_true(d1.same);
   310   do_check_false(d2.same);
   311   do_check_false(d3.same);
   312   do_check_false(d4.same);
   313   do_check_false(d5.same);
   314   do_check_false(d6.same);
   315   do_check_true(d7.same);
   316   do_check_true(d8.same);
   318   do_check_array_eq(d1.changed, []);
   319   do_check_array_eq(d2.changed, ["bar"]);
   320   do_check_array_eq(d3.changed, ["bar"]);
   321   do_check_array_eq(d4.changed, ["bar", "foo"]);
   322   do_check_array_eq(d5.changed, ["baz", "foo"]);
   323   do_check_array_eq(d6.changed, ["bar", "foo"]);
   325   run_next_test();
   326 });
   328 function run_test() {
   329   // Only do 1,000 to avoid a 5-second pause in test runs.
   330   test_time_keyFromString(1000);
   332   run_next_test();
   333 }

mercurial