tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 });