1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/sync/tests/unit/test_keys.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,333 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + * http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 + 1.7 +Cu.import("resource://services-sync/constants.js"); 1.8 +Cu.import("resource://services-sync/identity.js"); 1.9 +Cu.import("resource://services-sync/keys.js"); 1.10 +Cu.import("resource://services-sync/record.js"); 1.11 +Cu.import("resource://services-sync/util.js"); 1.12 + 1.13 +let collectionKeys = new CollectionKeyManager(); 1.14 + 1.15 +function sha256HMAC(message, key) { 1.16 + let h = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key); 1.17 + return Utils.digestBytes(message, h); 1.18 +} 1.19 + 1.20 +function do_check_array_eq(a1, a2) { 1.21 + do_check_eq(a1.length, a2.length); 1.22 + for (let i = 0; i < a1.length; ++i) { 1.23 + do_check_eq(a1[i], a2[i]); 1.24 + } 1.25 +} 1.26 + 1.27 +function do_check_keypair_eq(a, b) { 1.28 + do_check_eq(2, a.length); 1.29 + do_check_eq(2, b.length); 1.30 + do_check_eq(a[0], b[0]); 1.31 + do_check_eq(a[1], b[1]); 1.32 +} 1.33 + 1.34 +function test_time_keyFromString(iterations) { 1.35 + let k; 1.36 + let o; 1.37 + let b = new BulkKeyBundle("dummy"); 1.38 + let d = Utils.decodeKeyBase32("ababcdefabcdefabcdefabcdef"); 1.39 + b.generateRandom(); 1.40 + 1.41 + _("Running " + iterations + " iterations of hmacKeyObject + sha256HMAC."); 1.42 + for (let i = 0; i < iterations; ++i) { 1.43 + let k = b.hmacKeyObject; 1.44 + o = sha256HMAC(d, k); 1.45 + } 1.46 + do_check_true(!!o); 1.47 + _("Done."); 1.48 +} 1.49 + 1.50 +add_test(function test_set_invalid_values() { 1.51 + _("Ensure that setting invalid encryption and HMAC key values is caught."); 1.52 + 1.53 + let bundle = new BulkKeyBundle("foo"); 1.54 + 1.55 + let thrown = false; 1.56 + try { 1.57 + bundle.encryptionKey = null; 1.58 + } catch (ex) { 1.59 + thrown = true; 1.60 + do_check_eq(ex.message.indexOf("Encryption key can only be set to"), 0); 1.61 + } finally { 1.62 + do_check_true(thrown); 1.63 + thrown = false; 1.64 + } 1.65 + 1.66 + try { 1.67 + bundle.encryptionKey = ["trollololol"]; 1.68 + } catch (ex) { 1.69 + thrown = true; 1.70 + do_check_eq(ex.message.indexOf("Encryption key can only be set to"), 0); 1.71 + } finally { 1.72 + do_check_true(thrown); 1.73 + thrown = false; 1.74 + } 1.75 + 1.76 + try { 1.77 + bundle.hmacKey = Utils.generateRandomBytes(15); 1.78 + } catch (ex) { 1.79 + thrown = true; 1.80 + do_check_eq(ex.message.indexOf("HMAC key must be at least 128"), 0); 1.81 + } finally { 1.82 + do_check_true(thrown); 1.83 + thrown = false; 1.84 + } 1.85 + 1.86 + try { 1.87 + bundle.hmacKey = null; 1.88 + } catch (ex) { 1.89 + thrown = true; 1.90 + do_check_eq(ex.message.indexOf("HMAC key can only be set to string"), 0); 1.91 + } finally { 1.92 + do_check_true(thrown); 1.93 + thrown = false; 1.94 + } 1.95 + 1.96 + try { 1.97 + bundle.hmacKey = ["trollolol"]; 1.98 + } catch (ex) { 1.99 + thrown = true; 1.100 + do_check_eq(ex.message.indexOf("HMAC key can only be set to"), 0); 1.101 + } finally { 1.102 + do_check_true(thrown); 1.103 + thrown = false; 1.104 + } 1.105 + 1.106 + try { 1.107 + bundle.hmacKey = Utils.generateRandomBytes(15); 1.108 + } catch (ex) { 1.109 + thrown = true; 1.110 + do_check_eq(ex.message.indexOf("HMAC key must be at least 128"), 0); 1.111 + } finally { 1.112 + do_check_true(thrown); 1.113 + thrown = false; 1.114 + } 1.115 + 1.116 + run_next_test(); 1.117 +}); 1.118 + 1.119 +add_test(function test_repeated_hmac() { 1.120 + let testKey = "ababcdefabcdefabcdefabcdef"; 1.121 + let k = Utils.makeHMACKey("foo"); 1.122 + let one = sha256HMAC(Utils.decodeKeyBase32(testKey), k); 1.123 + let two = sha256HMAC(Utils.decodeKeyBase32(testKey), k); 1.124 + do_check_eq(one, two); 1.125 + 1.126 + run_next_test(); 1.127 +}); 1.128 + 1.129 +add_test(function test_sync_key_bundle_derivation() { 1.130 + _("Ensure derivation from known values works."); 1.131 + 1.132 + // The known values in this test were originally verified against Firefox 1.133 + // Home. 1.134 + let bundle = new SyncKeyBundle("st3fan", "q7ynpwq7vsc9m34hankbyi3s3i"); 1.135 + 1.136 + // These should be compared to the results from Home, as they once were. 1.137 + let e = "14b8c09fa84e92729ee695160af6e0385f8f6215a25d14906e1747bdaa2de426"; 1.138 + let h = "370e3566245d79fe602a3adb5137e42439cd2a571235197e0469d7d541b07875"; 1.139 + 1.140 + let realE = Utils.bytesAsHex(bundle.encryptionKey); 1.141 + let realH = Utils.bytesAsHex(bundle.hmacKey); 1.142 + 1.143 + _("Real E: " + realE); 1.144 + _("Real H: " + realH); 1.145 + do_check_eq(realH, h); 1.146 + do_check_eq(realE, e); 1.147 + 1.148 + run_next_test(); 1.149 +}); 1.150 + 1.151 +add_test(function test_keymanager() { 1.152 + let testKey = "ababcdefabcdefabcdefabcdef"; 1.153 + let username = "john@example.com"; 1.154 + 1.155 + // Decode the key here to mirror what generateEntry will do, 1.156 + // but pass it encoded into the KeyBundle call below. 1.157 + 1.158 + let sha256inputE = "" + HMAC_INPUT + username + "\x01"; 1.159 + let key = Utils.makeHMACKey(Utils.decodeKeyBase32(testKey)); 1.160 + let encryptKey = sha256HMAC(sha256inputE, key); 1.161 + 1.162 + let sha256inputH = encryptKey + HMAC_INPUT + username + "\x02"; 1.163 + let hmacKey = sha256HMAC(sha256inputH, key); 1.164 + 1.165 + // Encryption key is stored in base64 for WeaveCrypto convenience. 1.166 + do_check_eq(encryptKey, new SyncKeyBundle(username, testKey).encryptionKey); 1.167 + do_check_eq(hmacKey, new SyncKeyBundle(username, testKey).hmacKey); 1.168 + 1.169 + // Test with the same KeyBundle for both. 1.170 + let obj = new SyncKeyBundle(username, testKey); 1.171 + do_check_eq(hmacKey, obj.hmacKey); 1.172 + do_check_eq(encryptKey, obj.encryptionKey); 1.173 + 1.174 + run_next_test(); 1.175 +}); 1.176 + 1.177 +add_test(function test_collections_manager() { 1.178 + let log = Log.repository.getLogger("Test"); 1.179 + Log.repository.rootLogger.addAppender(new Log.DumpAppender()); 1.180 + 1.181 + let identity = new IdentityManager(); 1.182 + 1.183 + identity.account = "john@example.com"; 1.184 + identity.syncKey = "a-bbbbb-ccccc-ddddd-eeeee-fffff"; 1.185 + 1.186 + let keyBundle = identity.syncKeyBundle; 1.187 + 1.188 + /* 1.189 + * Build a test version of storage/crypto/keys. 1.190 + * Encrypt it with the sync key. 1.191 + * Pass it into the CollectionKeyManager. 1.192 + */ 1.193 + 1.194 + log.info("Building storage keys..."); 1.195 + let storage_keys = new CryptoWrapper("crypto", "keys"); 1.196 + let default_key64 = Svc.Crypto.generateRandomKey(); 1.197 + let default_hmac64 = Svc.Crypto.generateRandomKey(); 1.198 + let bookmarks_key64 = Svc.Crypto.generateRandomKey(); 1.199 + let bookmarks_hmac64 = Svc.Crypto.generateRandomKey(); 1.200 + 1.201 + storage_keys.cleartext = { 1.202 + "default": [default_key64, default_hmac64], 1.203 + "collections": {"bookmarks": [bookmarks_key64, bookmarks_hmac64]}, 1.204 + }; 1.205 + storage_keys.modified = Date.now()/1000; 1.206 + storage_keys.id = "keys"; 1.207 + 1.208 + log.info("Encrypting storage keys..."); 1.209 + 1.210 + // Use passphrase (sync key) itself to encrypt the key bundle. 1.211 + storage_keys.encrypt(keyBundle); 1.212 + 1.213 + // Sanity checking. 1.214 + do_check_true(null == storage_keys.cleartext); 1.215 + do_check_true(null != storage_keys.ciphertext); 1.216 + 1.217 + log.info("Updating collection keys."); 1.218 + 1.219 + // updateContents decrypts the object, releasing the payload for us to use. 1.220 + // Returns true, because the default key has changed. 1.221 + do_check_true(collectionKeys.updateContents(keyBundle, storage_keys)); 1.222 + let payload = storage_keys.cleartext; 1.223 + 1.224 + _("CK: " + JSON.stringify(collectionKeys._collections)); 1.225 + 1.226 + // Test that the CollectionKeyManager returns a similar WBO. 1.227 + let wbo = collectionKeys.asWBO("crypto", "keys"); 1.228 + 1.229 + _("WBO: " + JSON.stringify(wbo)); 1.230 + _("WBO cleartext: " + JSON.stringify(wbo.cleartext)); 1.231 + 1.232 + // Check the individual contents. 1.233 + do_check_eq(wbo.collection, "crypto"); 1.234 + do_check_eq(wbo.id, "keys"); 1.235 + do_check_eq(undefined, wbo.modified); 1.236 + do_check_eq(collectionKeys.lastModified, storage_keys.modified); 1.237 + do_check_true(!!wbo.cleartext.default); 1.238 + do_check_keypair_eq(payload.default, wbo.cleartext.default); 1.239 + do_check_keypair_eq(payload.collections.bookmarks, wbo.cleartext.collections.bookmarks); 1.240 + 1.241 + do_check_true('bookmarks' in collectionKeys._collections); 1.242 + do_check_false('tabs' in collectionKeys._collections); 1.243 + 1.244 + _("Updating contents twice with the same data doesn't proceed."); 1.245 + storage_keys.encrypt(keyBundle); 1.246 + do_check_false(collectionKeys.updateContents(keyBundle, storage_keys)); 1.247 + 1.248 + /* 1.249 + * Test that we get the right keys out when we ask for 1.250 + * a collection's tokens. 1.251 + */ 1.252 + let b1 = new BulkKeyBundle("bookmarks"); 1.253 + b1.keyPairB64 = [bookmarks_key64, bookmarks_hmac64]; 1.254 + let b2 = collectionKeys.keyForCollection("bookmarks"); 1.255 + do_check_keypair_eq(b1.keyPair, b2.keyPair); 1.256 + 1.257 + // Check key equality. 1.258 + do_check_true(b1.equals(b2)); 1.259 + do_check_true(b2.equals(b1)); 1.260 + 1.261 + b1 = new BulkKeyBundle("[default]"); 1.262 + b1.keyPairB64 = [default_key64, default_hmac64]; 1.263 + 1.264 + do_check_false(b1.equals(b2)); 1.265 + do_check_false(b2.equals(b1)); 1.266 + 1.267 + b2 = collectionKeys.keyForCollection(null); 1.268 + do_check_keypair_eq(b1.keyPair, b2.keyPair); 1.269 + 1.270 + /* 1.271 + * Checking for update times. 1.272 + */ 1.273 + let info_collections = {}; 1.274 + do_check_true(collectionKeys.updateNeeded(info_collections)); 1.275 + info_collections["crypto"] = 5000; 1.276 + do_check_false(collectionKeys.updateNeeded(info_collections)); 1.277 + info_collections["crypto"] = 1 + (Date.now()/1000); // Add one in case computers are fast! 1.278 + do_check_true(collectionKeys.updateNeeded(info_collections)); 1.279 + 1.280 + collectionKeys.lastModified = null; 1.281 + do_check_true(collectionKeys.updateNeeded({})); 1.282 + 1.283 + /* 1.284 + * Check _compareKeyBundleCollections. 1.285 + */ 1.286 + function newBundle(name) { 1.287 + let r = new BulkKeyBundle(name); 1.288 + r.generateRandom(); 1.289 + return r; 1.290 + } 1.291 + let k1 = newBundle("k1"); 1.292 + let k2 = newBundle("k2"); 1.293 + let k3 = newBundle("k3"); 1.294 + let k4 = newBundle("k4"); 1.295 + let k5 = newBundle("k5"); 1.296 + let coll1 = {"foo": k1, "bar": k2}; 1.297 + let coll2 = {"foo": k1, "bar": k2}; 1.298 + let coll3 = {"foo": k1, "bar": k3}; 1.299 + let coll4 = {"foo": k4}; 1.300 + let coll5 = {"baz": k5, "bar": k2}; 1.301 + let coll6 = {}; 1.302 + 1.303 + let d1 = collectionKeys._compareKeyBundleCollections(coll1, coll2); // [] 1.304 + let d2 = collectionKeys._compareKeyBundleCollections(coll1, coll3); // ["bar"] 1.305 + let d3 = collectionKeys._compareKeyBundleCollections(coll3, coll2); // ["bar"] 1.306 + let d4 = collectionKeys._compareKeyBundleCollections(coll1, coll4); // ["bar", "foo"] 1.307 + let d5 = collectionKeys._compareKeyBundleCollections(coll5, coll2); // ["baz", "foo"] 1.308 + let d6 = collectionKeys._compareKeyBundleCollections(coll6, coll1); // ["bar", "foo"] 1.309 + let d7 = collectionKeys._compareKeyBundleCollections(coll5, coll5); // [] 1.310 + let d8 = collectionKeys._compareKeyBundleCollections(coll6, coll6); // [] 1.311 + 1.312 + do_check_true(d1.same); 1.313 + do_check_false(d2.same); 1.314 + do_check_false(d3.same); 1.315 + do_check_false(d4.same); 1.316 + do_check_false(d5.same); 1.317 + do_check_false(d6.same); 1.318 + do_check_true(d7.same); 1.319 + do_check_true(d8.same); 1.320 + 1.321 + do_check_array_eq(d1.changed, []); 1.322 + do_check_array_eq(d2.changed, ["bar"]); 1.323 + do_check_array_eq(d3.changed, ["bar"]); 1.324 + do_check_array_eq(d4.changed, ["bar", "foo"]); 1.325 + do_check_array_eq(d5.changed, ["baz", "foo"]); 1.326 + do_check_array_eq(d6.changed, ["bar", "foo"]); 1.327 + 1.328 + run_next_test(); 1.329 +}); 1.330 + 1.331 +function run_test() { 1.332 + // Only do 1,000 to avoid a 5-second pause in test runs. 1.333 + test_time_keyFromString(1000); 1.334 + 1.335 + run_next_test(); 1.336 +}