Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
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 }