Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
"use strict";
const { ArchiveEncryptionState } = ChromeUtils.importESModule(
"resource:///modules/backup/ArchiveEncryptionState.sys.mjs"
);
const { ArchiveEncryptor, ArchiveDecryptor } = ChromeUtils.importESModule(
"resource:///modules/backup/ArchiveEncryption.sys.mjs"
);
const { ArchiveUtils } = ChromeUtils.importESModule(
"resource:///modules/backup/ArchiveUtils.sys.mjs"
);
const TEST_RECOVERY_CODE = "This is my recovery code.";
const FAKE_BYTES_AMOUNT = 1000;
let fakeBytes = null;
add_setup(async () => {
fakeBytes = new Uint8Array(FAKE_BYTES_AMOUNT);
// seededRandomNumberGenerator is defined in head.js, but eslint doesn't seem
// happy about it. Maybe that's because it's a generator function.
// eslint-disable-next-line no-undef
let gen = seededRandomNumberGenerator();
for (let i = 0; i < FAKE_BYTES_AMOUNT; ++i) {
fakeBytes.set(gen.next().value, i);
}
});
/**
* Tests that we can construct an ArchiveEncryptor by way of the properties
* of an ArchiveEncryptionState.
*/
add_task(async function test_ArchiveEncryptor_initializer() {
let { instance: encState } = await ArchiveEncryptionState.initialize(
TEST_RECOVERY_CODE
);
let encryptor = await ArchiveEncryptor.initialize(
encState.publicKey,
encState.backupAuthKey
);
Assert.ok(encryptor, "An ArchiveEncryptor was successfully constructed");
});
/**
* Tests that we can encrypt a single chunk of bytes.
*/
add_task(async function test_ArchiveEncryption_single_chunk() {
let { instance: encState } = await ArchiveEncryptionState.initialize(
TEST_RECOVERY_CODE
);
let encryptor = await ArchiveEncryptor.initialize(
encState.publicKey,
encState.backupAuthKey
);
const TEST_METADATA = { test: "hello!" };
let jsonBlock = await encryptor.confirm(
TEST_METADATA,
encState.wrappedSecrets,
encState.salt,
encState.nonce
);
// Ensure that the JSON block can be serialized to string, and deserialized
// again.
jsonBlock = JSON.parse(JSON.stringify(jsonBlock));
let encryptedBytes = await encryptor.encrypt(
fakeBytes,
true /* isLastChunk */
);
// Ensure the the encrypted bytes do not match the plaintext bytes.
Assert.greater(
encryptedBytes.byteLength,
fakeBytes.byteLength,
"Encrypted bytes should be larger"
);
assertUint8ArraysSimilarity(
encryptedBytes,
fakeBytes,
false /* expectSimilar */
);
let decryptor = await ArchiveDecryptor.initialize(
TEST_RECOVERY_CODE,
jsonBlock
);
Assert.ok(decryptor, "Got back an initialized ArchiveDecryptor");
let decryptedBytes = await decryptor.decrypt(encryptedBytes, true);
Assert.equal(
decryptedBytes.byteLength,
fakeBytes.byteLength,
"Decrypted bytes should have original length"
);
assertUint8ArraysSimilarity(
decryptedBytes,
fakeBytes,
true /* expectSimilar */
);
});
/**
* Tests that we can encrypt an unevenly sized set of chunks.
*/
add_task(async function test_ArchiveEncryption_uneven_chunks() {
let { instance: encState } = await ArchiveEncryptionState.initialize(
TEST_RECOVERY_CODE
);
let encryptor = await ArchiveEncryptor.initialize(
encState.publicKey,
encState.backupAuthKey
);
const TEST_METADATA = { test: "hello!" };
let jsonBlock = await encryptor.confirm(
TEST_METADATA,
encState.wrappedSecrets,
encState.salt,
encState.nonce
);
// Ensure that the JSON block can be serialized to string, and deserialized
// again.
jsonBlock = JSON.parse(JSON.stringify(jsonBlock));
// FAKE_BYTES_AMOUNT / 3 shouldn't divide cleanly. So our chunks will have the
// following byte indices:
//
// - 0, 332 (333 bytes)
// - 333, 666 (333 bytes)
// - 667, 999 (332 bytes)
//
// Note that subarray's "end" argument is _exclusive_.
let sandbox = sinon.createSandbox();
sandbox.stub(ArchiveUtils, "ARCHIVE_CHUNK_MAX_BYTES_SIZE").get(() => {
return 333;
});
let firstChunk = fakeBytes.subarray(0, 333);
Assert.equal(firstChunk.byteLength, 333);
let secondChunk = fakeBytes.subarray(333, 666);
Assert.equal(secondChunk.byteLength, 333);
let thirdChunk = fakeBytes.subarray(667, 999);
Assert.equal(thirdChunk.byteLength, 332);
let encryptedFirstChunk = await encryptor.encrypt(firstChunk);
let encryptedSecondChunk = await encryptor.encrypt(secondChunk);
let encryptedThirdChunk = await encryptor.encrypt(
thirdChunk,
true /*isLastChunk */
);
let encryptedPairsToCompare = [
[firstChunk, encryptedFirstChunk],
[secondChunk, encryptedSecondChunk],
[thirdChunk, encryptedThirdChunk],
];
for (let [chunk, encryptedChunk] of encryptedPairsToCompare) {
assertUint8ArraysSimilarity(
chunk,
encryptedChunk,
false /* expectSimilar */
);
}
let decryptor = await ArchiveDecryptor.initialize(
TEST_RECOVERY_CODE,
jsonBlock
);
Assert.ok(decryptor, "Got back an initialized ArchiveDecryptor");
let decryptedFirstChunk = await decryptor.decrypt(encryptedFirstChunk);
let decryptedSecondChunk = await decryptor.decrypt(encryptedSecondChunk);
let decryptedThirdChunk = await decryptor.decrypt(
encryptedThirdChunk,
true /* isLastChunk */
);
let decryptedPairsToCompare = [
[firstChunk, decryptedFirstChunk],
[secondChunk, decryptedSecondChunk],
[thirdChunk, decryptedThirdChunk],
];
for (let [chunk, decryptedChunk] of decryptedPairsToCompare) {
Assert.equal(
chunk.byteLength,
decryptedChunk.byteLength,
"Decrypted bytes should have original length"
);
assertUint8ArraysSimilarity(
chunk,
decryptedChunk,
true /* expectSimilar */
);
}
sandbox.restore();
});
/**
* Tests that we can encrypt an even sized set of chunks.
*/
add_task(async function test_ArchiveEncryption_even_chunks() {
let { instance: encState } = await ArchiveEncryptionState.initialize(
TEST_RECOVERY_CODE
);
let encryptor = await ArchiveEncryptor.initialize(
encState.publicKey,
encState.backupAuthKey
);
const TEST_METADATA = { test: "hello!" };
let jsonBlock = await encryptor.confirm(
TEST_METADATA,
encState.wrappedSecrets,
encState.salt,
encState.nonce
);
// Ensure that the JSON block can be serialized to string, and deserialized
// again.
jsonBlock = JSON.parse(JSON.stringify(jsonBlock));
// FAKE_BYTES_AMOUNT / 2 should divide evenly. So our chunks will have the
// following byte indices:
//
// - 0, 499 (500 bytes)
// - 500, 999 (500 bytes)
//
// Note that subarray's "end" argument is _exclusive_.
let sandbox = sinon.createSandbox();
sandbox.stub(ArchiveUtils, "ARCHIVE_CHUNK_MAX_BYTES_SIZE").get(() => {
return 500;
});
let firstChunk = fakeBytes.subarray(0, 500);
Assert.equal(firstChunk.byteLength, 500);
let secondChunk = fakeBytes.subarray(500);
Assert.equal(secondChunk.byteLength, 500);
let encryptedFirstChunk = await encryptor.encrypt(firstChunk);
let encryptedSecondChunk = await encryptor.encrypt(
secondChunk,
true /*isLastChunk */
);
let encryptedPairsToCompare = [
[firstChunk, encryptedFirstChunk],
[secondChunk, encryptedSecondChunk],
];
for (let [chunk, encryptedChunk] of encryptedPairsToCompare) {
assertUint8ArraysSimilarity(
chunk,
encryptedChunk,
false /* expectSimilar */
);
}
let decryptor = await ArchiveDecryptor.initialize(
TEST_RECOVERY_CODE,
jsonBlock
);
Assert.ok(decryptor, "Got back an initialized ArchiveDecryptor");
let decryptedFirstChunk = await decryptor.decrypt(encryptedFirstChunk);
let decryptedSecondChunk = await decryptor.decrypt(
encryptedSecondChunk,
true /* isLastChunk */
);
let decryptedPairsToCompare = [
[firstChunk, decryptedFirstChunk],
[secondChunk, decryptedSecondChunk],
];
for (let [chunk, decryptedChunk] of decryptedPairsToCompare) {
Assert.equal(
chunk.byteLength,
decryptedChunk.byteLength,
"Decrypted bytes should have original length"
);
assertUint8ArraysSimilarity(
chunk,
decryptedChunk,
true /* expectSimilar */
);
}
sandbox.restore();
});
/**
* Tests that we cannot decrypt with the wrong recovery code.
*/
add_task(async function test_ArchiveEncryption_wrong_recoveryCode() {
let { instance: encState } = await ArchiveEncryptionState.initialize(
TEST_RECOVERY_CODE
);
let encryptor = await ArchiveEncryptor.initialize(
encState.publicKey,
encState.backupAuthKey
);
const TEST_METADATA = { test: "hello!" };
let jsonBlock = await encryptor.confirm(
TEST_METADATA,
encState.wrappedSecrets,
encState.salt,
encState.nonce
);
// We don't actually care about the encrypted bytes, since we're just
// testing that ArchiveDecryptor won't accept an incorrect recovery code.
await encryptor.encrypt(fakeBytes, true /* isLastChunk */);
await Assert.rejects(
ArchiveDecryptor.initialize("Wrong recovery code", jsonBlock),
/Unauthenticated/
);
});