test_records_crypto.js (6171B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 const { CollectionKeyManager, CryptoWrapper } = ChromeUtils.importESModule( 5 "resource://services-sync/record.sys.mjs" 6 ); 7 const { Service } = ChromeUtils.importESModule( 8 "resource://services-sync/service.sys.mjs" 9 ); 10 11 var cryptoWrap; 12 13 function crypted_resource_handler(metadata, response) { 14 let obj = { 15 id: "resource", 16 modified: cryptoWrap.modified, 17 payload: JSON.stringify(cryptoWrap.payload), 18 }; 19 return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response); 20 } 21 22 function prepareCryptoWrap(collection, id) { 23 let w = new CryptoWrapper(); 24 w.cleartext.stuff = "my payload here"; 25 w.collection = collection; 26 w.id = id; 27 return w; 28 } 29 30 add_task(async function test_records_crypto() { 31 let server; 32 33 await configureIdentity({ username: "john@example.com" }); 34 let keyBundle = Service.identity.syncKeyBundle; 35 36 try { 37 let log = Log.repository.getLogger("Test"); 38 Log.repository.rootLogger.addAppender(new Log.DumpAppender()); 39 40 log.info("Setting up server and authenticator"); 41 42 server = httpd_setup({ "/steam/resource": crypted_resource_handler }); 43 44 log.info("Creating a record"); 45 46 cryptoWrap = prepareCryptoWrap("steam", "resource"); 47 48 log.info("cryptoWrap: " + cryptoWrap.toString()); 49 50 log.info("Encrypting a record"); 51 52 await cryptoWrap.encrypt(keyBundle); 53 log.info("Ciphertext is " + cryptoWrap.ciphertext); 54 Assert.notEqual(cryptoWrap.ciphertext, null); 55 56 let firstIV = cryptoWrap.IV; 57 58 log.info("Decrypting the record"); 59 60 let payload = await cryptoWrap.decrypt(keyBundle); 61 Assert.equal(payload.stuff, "my payload here"); 62 Assert.notEqual(payload, cryptoWrap.payload); // wrap.data.payload is the encrypted one 63 64 log.info("Make sure multiple decrypts cause failures"); 65 let error = ""; 66 try { 67 payload = await cryptoWrap.decrypt(keyBundle); 68 } catch (ex) { 69 error = ex; 70 } 71 Assert.equal(error.message, "No ciphertext: nothing to decrypt?"); 72 73 log.info("Re-encrypting the record with alternate payload"); 74 75 cryptoWrap.cleartext.stuff = "another payload"; 76 await cryptoWrap.encrypt(keyBundle); 77 let secondIV = cryptoWrap.IV; 78 payload = await cryptoWrap.decrypt(keyBundle); 79 Assert.equal(payload.stuff, "another payload"); 80 81 log.info("Make sure multiple encrypts use different IVs"); 82 Assert.notEqual(firstIV, secondIV); 83 84 log.info(await "Make sure differing ids cause failures"); 85 await cryptoWrap.encrypt(keyBundle); 86 cryptoWrap.data.id = "other"; 87 error = ""; 88 try { 89 await cryptoWrap.decrypt(keyBundle); 90 } catch (ex) { 91 error = ex; 92 } 93 Assert.equal(error.message, "Record id mismatch: resource != other"); 94 95 log.info("Make sure wrong hmacs cause failures"); 96 await cryptoWrap.encrypt(keyBundle); 97 cryptoWrap.hmac = "foo"; 98 error = ""; 99 try { 100 await cryptoWrap.decrypt(keyBundle); 101 } catch (ex) { 102 error = ex; 103 } 104 Assert.equal( 105 error.message.substr(0, 42), 106 "Record SHA256 HMAC mismatch: should be foo" 107 ); 108 109 // Checking per-collection keys and default key handling. 110 111 await generateNewKeys(Service.collectionKeys); 112 let bookmarkItem = prepareCryptoWrap("bookmarks", "foo"); 113 await bookmarkItem.encrypt( 114 Service.collectionKeys.keyForCollection("bookmarks") 115 ); 116 log.info("Ciphertext is " + bookmarkItem.ciphertext); 117 Assert.notEqual(bookmarkItem.ciphertext, null); 118 log.info("Decrypting the record explicitly with the default key."); 119 Assert.equal( 120 (await bookmarkItem.decrypt(Service.collectionKeys._default)).stuff, 121 "my payload here" 122 ); 123 124 // Per-collection keys. 125 // Generate a key for "bookmarks". 126 await generateNewKeys(Service.collectionKeys, ["bookmarks"]); 127 bookmarkItem = prepareCryptoWrap("bookmarks", "foo"); 128 Assert.equal(bookmarkItem.collection, "bookmarks"); 129 130 // Encrypt. This'll use the "bookmarks" encryption key, because we have a 131 // special key for it. The same key will need to be used for decryption. 132 await bookmarkItem.encrypt( 133 Service.collectionKeys.keyForCollection("bookmarks") 134 ); 135 Assert.notEqual(bookmarkItem.ciphertext, null); 136 137 // Attempt to use the default key, because this is a collision that could 138 // conceivably occur in the real world. Decryption will error, because 139 // it's not the bookmarks key. 140 let err; 141 try { 142 await bookmarkItem.decrypt(Service.collectionKeys._default); 143 } catch (ex) { 144 err = ex; 145 } 146 Assert.equal("Record SHA256 HMAC mismatch", err.message.substr(0, 27)); 147 148 // Explicitly check that it's using the bookmarks key. 149 // This should succeed. 150 Assert.equal( 151 ( 152 await bookmarkItem.decrypt( 153 Service.collectionKeys.keyForCollection("bookmarks") 154 ) 155 ).stuff, 156 "my payload here" 157 ); 158 159 Assert.ok(Service.collectionKeys.hasKeysFor(["bookmarks"])); 160 161 // Add a key for some new collection and verify that it isn't the 162 // default key. 163 Assert.ok(!Service.collectionKeys.hasKeysFor(["forms"])); 164 Assert.ok(!Service.collectionKeys.hasKeysFor(["bookmarks", "forms"])); 165 let oldFormsKey = Service.collectionKeys.keyForCollection("forms"); 166 Assert.equal(oldFormsKey, Service.collectionKeys._default); 167 let newKeys = await Service.collectionKeys.ensureKeysFor(["forms"]); 168 Assert.ok(newKeys.hasKeysFor(["forms"])); 169 Assert.ok(newKeys.hasKeysFor(["bookmarks", "forms"])); 170 let newFormsKey = newKeys.keyForCollection("forms"); 171 Assert.notEqual(newFormsKey, oldFormsKey); 172 173 // Verify that this doesn't overwrite keys 174 let regetKeys = await newKeys.ensureKeysFor(["forms"]); 175 Assert.equal(regetKeys.keyForCollection("forms"), newFormsKey); 176 177 const emptyKeys = new CollectionKeyManager(); 178 payload = { 179 default: Service.collectionKeys._default.keyPairB64, 180 collections: {}, 181 }; 182 // Verify that not passing `modified` doesn't throw 183 emptyKeys.setContents(payload, null); 184 185 log.info("Done!"); 186 } finally { 187 await promiseStopServer(server); 188 } 189 });