test_ArchiveEncryption.js (9142B)
1 /* Any copyright is dedicated to the Public Domain. 2 https://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { ArchiveEncryptionState } = ChromeUtils.importESModule( 7 "resource:///modules/backup/ArchiveEncryptionState.sys.mjs" 8 ); 9 const { ArchiveEncryptor, ArchiveDecryptor } = ChromeUtils.importESModule( 10 "resource:///modules/backup/ArchiveEncryption.sys.mjs" 11 ); 12 const { ArchiveUtils } = ChromeUtils.importESModule( 13 "resource:///modules/backup/ArchiveUtils.sys.mjs" 14 ); 15 16 const TEST_RECOVERY_CODE = "This is my recovery code."; 17 18 const FAKE_BYTES_AMOUNT = 1000; 19 20 let fakeBytes = null; 21 22 add_setup(async () => { 23 fakeBytes = new Uint8Array(FAKE_BYTES_AMOUNT); 24 // seededRandomNumberGenerator is defined in head.js, but eslint doesn't seem 25 // happy about it. Maybe that's because it's a generator function. 26 // eslint-disable-next-line no-undef 27 let gen = seededRandomNumberGenerator(); 28 for (let i = 0; i < FAKE_BYTES_AMOUNT; ++i) { 29 fakeBytes.set(gen.next().value, i); 30 } 31 }); 32 33 /** 34 * Tests that we can construct an ArchiveEncryptor by way of the properties 35 * of an ArchiveEncryptionState. 36 */ 37 add_task(async function test_ArchiveEncryptor_initializer() { 38 let { instance: encState } = 39 await ArchiveEncryptionState.initialize(TEST_RECOVERY_CODE); 40 let encryptor = await ArchiveEncryptor.initialize( 41 encState.publicKey, 42 encState.backupAuthKey 43 ); 44 Assert.ok(encryptor, "An ArchiveEncryptor was successfully constructed"); 45 }); 46 47 /** 48 * Tests that we can encrypt a single chunk of bytes. 49 */ 50 add_task(async function test_ArchiveEncryption_single_chunk() { 51 let { instance: encState } = 52 await ArchiveEncryptionState.initialize(TEST_RECOVERY_CODE); 53 let encryptor = await ArchiveEncryptor.initialize( 54 encState.publicKey, 55 encState.backupAuthKey 56 ); 57 58 const TEST_METADATA = { test: "hello!" }; 59 let jsonBlock = await encryptor.confirm( 60 TEST_METADATA, 61 encState.wrappedSecrets, 62 encState.salt, 63 encState.nonce 64 ); 65 // Ensure that the JSON block can be serialized to string, and deserialized 66 // again. 67 jsonBlock = JSON.parse(JSON.stringify(jsonBlock)); 68 69 let encryptedBytes = await encryptor.encrypt( 70 fakeBytes, 71 true /* isLastChunk */ 72 ); 73 74 // Ensure the the encrypted bytes do not match the plaintext bytes. 75 Assert.greater( 76 encryptedBytes.byteLength, 77 fakeBytes.byteLength, 78 "Encrypted bytes should be larger" 79 ); 80 81 assertUint8ArraysSimilarity( 82 encryptedBytes, 83 fakeBytes, 84 false /* expectSimilar */ 85 ); 86 87 let decryptor = await ArchiveDecryptor.initialize( 88 TEST_RECOVERY_CODE, 89 jsonBlock 90 ); 91 Assert.ok(decryptor, "Got back an initialized ArchiveDecryptor"); 92 93 let decryptedBytes = await decryptor.decrypt(encryptedBytes, true); 94 95 Assert.equal( 96 decryptedBytes.byteLength, 97 fakeBytes.byteLength, 98 "Decrypted bytes should have original length" 99 ); 100 101 assertUint8ArraysSimilarity( 102 decryptedBytes, 103 fakeBytes, 104 true /* expectSimilar */ 105 ); 106 }); 107 108 /** 109 * Tests that we can encrypt an unevenly sized set of chunks. 110 */ 111 add_task(async function test_ArchiveEncryption_uneven_chunks() { 112 let { instance: encState } = 113 await ArchiveEncryptionState.initialize(TEST_RECOVERY_CODE); 114 let encryptor = await ArchiveEncryptor.initialize( 115 encState.publicKey, 116 encState.backupAuthKey 117 ); 118 119 const TEST_METADATA = { test: "hello!" }; 120 let jsonBlock = await encryptor.confirm( 121 TEST_METADATA, 122 encState.wrappedSecrets, 123 encState.salt, 124 encState.nonce 125 ); 126 // Ensure that the JSON block can be serialized to string, and deserialized 127 // again. 128 jsonBlock = JSON.parse(JSON.stringify(jsonBlock)); 129 130 // FAKE_BYTES_AMOUNT / 3 shouldn't divide cleanly. So our chunks will have the 131 // following byte indices: 132 // 133 // - 0, 332 (333 bytes) 134 // - 333, 666 (333 bytes) 135 // - 667, 999 (332 bytes) 136 // 137 // Note that subarray's "end" argument is _exclusive_. 138 let sandbox = sinon.createSandbox(); 139 sandbox.stub(ArchiveUtils, "ARCHIVE_CHUNK_MAX_BYTES_SIZE").get(() => { 140 return 333; 141 }); 142 143 let firstChunk = fakeBytes.subarray(0, 333); 144 Assert.equal(firstChunk.byteLength, 333); 145 let secondChunk = fakeBytes.subarray(333, 666); 146 Assert.equal(secondChunk.byteLength, 333); 147 let thirdChunk = fakeBytes.subarray(667, 999); 148 Assert.equal(thirdChunk.byteLength, 332); 149 150 let encryptedFirstChunk = await encryptor.encrypt(firstChunk); 151 let encryptedSecondChunk = await encryptor.encrypt(secondChunk); 152 let encryptedThirdChunk = await encryptor.encrypt( 153 thirdChunk, 154 true /*isLastChunk */ 155 ); 156 157 let encryptedPairsToCompare = [ 158 [firstChunk, encryptedFirstChunk], 159 [secondChunk, encryptedSecondChunk], 160 [thirdChunk, encryptedThirdChunk], 161 ]; 162 163 for (let [chunk, encryptedChunk] of encryptedPairsToCompare) { 164 assertUint8ArraysSimilarity( 165 chunk, 166 encryptedChunk, 167 false /* expectSimilar */ 168 ); 169 } 170 171 let decryptor = await ArchiveDecryptor.initialize( 172 TEST_RECOVERY_CODE, 173 jsonBlock 174 ); 175 Assert.ok(decryptor, "Got back an initialized ArchiveDecryptor"); 176 177 let decryptedFirstChunk = await decryptor.decrypt(encryptedFirstChunk); 178 let decryptedSecondChunk = await decryptor.decrypt(encryptedSecondChunk); 179 let decryptedThirdChunk = await decryptor.decrypt( 180 encryptedThirdChunk, 181 true /* isLastChunk */ 182 ); 183 184 let decryptedPairsToCompare = [ 185 [firstChunk, decryptedFirstChunk], 186 [secondChunk, decryptedSecondChunk], 187 [thirdChunk, decryptedThirdChunk], 188 ]; 189 190 for (let [chunk, decryptedChunk] of decryptedPairsToCompare) { 191 Assert.equal( 192 chunk.byteLength, 193 decryptedChunk.byteLength, 194 "Decrypted bytes should have original length" 195 ); 196 assertUint8ArraysSimilarity( 197 chunk, 198 decryptedChunk, 199 true /* expectSimilar */ 200 ); 201 } 202 sandbox.restore(); 203 }); 204 205 /** 206 * Tests that we can encrypt an even sized set of chunks. 207 */ 208 add_task(async function test_ArchiveEncryption_even_chunks() { 209 let { instance: encState } = 210 await ArchiveEncryptionState.initialize(TEST_RECOVERY_CODE); 211 let encryptor = await ArchiveEncryptor.initialize( 212 encState.publicKey, 213 encState.backupAuthKey 214 ); 215 216 const TEST_METADATA = { test: "hello!" }; 217 let jsonBlock = await encryptor.confirm( 218 TEST_METADATA, 219 encState.wrappedSecrets, 220 encState.salt, 221 encState.nonce 222 ); 223 // Ensure that the JSON block can be serialized to string, and deserialized 224 // again. 225 jsonBlock = JSON.parse(JSON.stringify(jsonBlock)); 226 227 // FAKE_BYTES_AMOUNT / 2 should divide evenly. So our chunks will have the 228 // following byte indices: 229 // 230 // - 0, 499 (500 bytes) 231 // - 500, 999 (500 bytes) 232 // 233 // Note that subarray's "end" argument is _exclusive_. 234 let sandbox = sinon.createSandbox(); 235 sandbox.stub(ArchiveUtils, "ARCHIVE_CHUNK_MAX_BYTES_SIZE").get(() => { 236 return 500; 237 }); 238 239 let firstChunk = fakeBytes.subarray(0, 500); 240 Assert.equal(firstChunk.byteLength, 500); 241 let secondChunk = fakeBytes.subarray(500); 242 Assert.equal(secondChunk.byteLength, 500); 243 244 let encryptedFirstChunk = await encryptor.encrypt(firstChunk); 245 let encryptedSecondChunk = await encryptor.encrypt( 246 secondChunk, 247 true /*isLastChunk */ 248 ); 249 250 let encryptedPairsToCompare = [ 251 [firstChunk, encryptedFirstChunk], 252 [secondChunk, encryptedSecondChunk], 253 ]; 254 255 for (let [chunk, encryptedChunk] of encryptedPairsToCompare) { 256 assertUint8ArraysSimilarity( 257 chunk, 258 encryptedChunk, 259 false /* expectSimilar */ 260 ); 261 } 262 263 let decryptor = await ArchiveDecryptor.initialize( 264 TEST_RECOVERY_CODE, 265 jsonBlock 266 ); 267 Assert.ok(decryptor, "Got back an initialized ArchiveDecryptor"); 268 269 let decryptedFirstChunk = await decryptor.decrypt(encryptedFirstChunk); 270 let decryptedSecondChunk = await decryptor.decrypt( 271 encryptedSecondChunk, 272 true /* isLastChunk */ 273 ); 274 275 let decryptedPairsToCompare = [ 276 [firstChunk, decryptedFirstChunk], 277 [secondChunk, decryptedSecondChunk], 278 ]; 279 280 for (let [chunk, decryptedChunk] of decryptedPairsToCompare) { 281 Assert.equal( 282 chunk.byteLength, 283 decryptedChunk.byteLength, 284 "Decrypted bytes should have original length" 285 ); 286 assertUint8ArraysSimilarity( 287 chunk, 288 decryptedChunk, 289 true /* expectSimilar */ 290 ); 291 } 292 sandbox.restore(); 293 }); 294 295 /** 296 * Tests that we cannot decrypt with the wrong recovery code. 297 */ 298 add_task(async function test_ArchiveEncryption_wrong_recoveryCode() { 299 let { instance: encState } = 300 await ArchiveEncryptionState.initialize(TEST_RECOVERY_CODE); 301 let encryptor = await ArchiveEncryptor.initialize( 302 encState.publicKey, 303 encState.backupAuthKey 304 ); 305 306 const TEST_METADATA = { test: "hello!" }; 307 let jsonBlock = await encryptor.confirm( 308 TEST_METADATA, 309 encState.wrappedSecrets, 310 encState.salt, 311 encState.nonce 312 ); 313 314 // We don't actually care about the encrypted bytes, since we're just 315 // testing that ArchiveDecryptor won't accept an incorrect recovery code. 316 await encryptor.encrypt(fakeBytes, true /* isLastChunk */); 317 318 await Assert.rejects( 319 ArchiveDecryptor.initialize("Wrong recovery code", jsonBlock), 320 /Unauthenticated/ 321 ); 322 });