/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const { Weave } = ChromeUtils.importESModule(
"resource://services-sync/main.sys.mjs"
);
const { HistoryEngine } = ChromeUtils.importESModule(
"resource://services-sync/engines/history.sys.mjs"
);
const { CryptoWrapper, WBORecord } = ChromeUtils.importESModule(
"resource://services-sync/record.sys.mjs"
);
const { Service } = ChromeUtils.importESModule(
"resource://services-sync/service.sys.mjs"
);
add_task(async
function test_locally_changed_keys() {
enableValidationPrefs();
let hmacErrorCount = 0;
function counting(f) {
return async
function () {
hmacErrorCount++;
return f.call(
this);
};
}
Service.handleHMACEvent = counting(Service.handleHMACEvent);
let server =
new SyncServer();
let johndoe = server.registerUser(
"johndoe",
"password");
johndoe.createContents({
meta: {},
crypto: {},
clients: {},
});
server.start();
try {
Svc.PrefBranch.setStringPref(
"registerEngines",
"Tab");
await configureIdentity({ username:
"johndoe" }, server);
// We aren't doing a .login yet, so fudge the cluster URL.
Service.clusterURL = Service.identity._token.endpoint;
await Service.engineManager.register(HistoryEngine);
// Disable addon sync because AddonManager won't be initialized here.
await Service.engineManager.unregister(
"addons");
await Service.engineManager.unregister(
"extension-storage");
async
function corrupt_local_keys() {
Service.collectionKeys._
default.keyPair = [
await Weave.Crypto.generateRandomKey(),
await Weave.Crypto.generateRandomKey(),
];
}
_(
"Setting meta.");
// Bump version on the server.
let m =
new WBORecord(
"meta",
"global");
m.payload = {
syncID:
"foooooooooooooooooooooooooo",
storageVersion: STORAGE_VERSION,
};
await m.upload(Service.resource(Service.metaURL));
_(
"New meta/global: " +
JSON.stringify(johndoe.collection(
"meta").wbo(
"global"))
);
// Upload keys.
await generateNewKeys(Service.collectionKeys);
let serverKeys = Service.collectionKeys.asWBO(
"crypto",
"keys");
await serverKeys.encrypt(Service.identity.syncKeyBundle);
Assert.ok(
(await serverKeys.upload(Service.resource(Service.cryptoKeysURL))).success
);
// Check that login works.
Assert.ok(await Service.login());
Assert.ok(Service.isLoggedIn);
// Sync should upload records.
await sync_and_validate_telem();
// Tabs exist.
_(
"Tabs modified: " + johndoe.modified(
"tabs"));
Assert.ok(johndoe.modified(
"tabs") > 0);
// Let's create some server side history records.
let liveKeys = Service.collectionKeys.keyForCollection(
"history");
_(
"Keys now: " + liveKeys.keyPair);
let visitType = Ci.nsINavHistoryService.TRANSITION_LINK;
let history = johndoe.createCollection(
"history");
for (let i = 0; i < 5; i++) {
let id =
"record-no--" + i;
let modified = Date.now() / 1000 - 60 * (i + 10);
let w =
new CryptoWrapper(
"history",
"id");
w.cleartext = {
id,
histUri:
"http://foo/bar?" + id,
title: id,
sortindex: i,
visits: [{ date: (modified - 5) * 1000000, type: visitType }],
deleted:
false,
};
await w.encrypt(liveKeys);
let payload = { ciphertext: w.ciphertext, IV: w.IV, hmac: w.hmac };
history.insert(id, payload, modified);
}
history.timestamp = Date.now() / 1000;
let old_key_time = johndoe.modified(
"crypto");
_(
"Old key time: " + old_key_time);
// Check that we can decrypt one.
let rec =
new CryptoWrapper(
"history",
"record-no--0");
await rec.fetch(
Service.resource(Service.storageURL +
"history/record-no--0")
);
_(JSON.stringify(rec));
Assert.ok(!!(await rec.decrypt(liveKeys)));
Assert.equal(hmacErrorCount, 0);
// Fill local key cache with bad data.
await corrupt_local_keys();
_(
"Keys now: " + Service.collectionKeys.keyForCollection(
"history").keyPair
);
Assert.equal(hmacErrorCount, 0);
_(
"HMAC error count: " + hmacErrorCount);
// Now syncing should succeed, after one HMAC error.
await sync_and_validate_telem(ping => {
Assert.equal(
ping.engines.find(e => e.name ==
"history").incoming.applied,
5
);
});
Assert.equal(hmacErrorCount, 1);
_(
"Keys now: " + Service.collectionKeys.keyForCollection(
"history").keyPair
);
// And look! We downloaded history!
Assert.ok(
await PlacesUtils.history.hasVisits(
"http://foo/bar?record-no--0")
);
Assert.ok(
await PlacesUtils.history.hasVisits(
"http://foo/bar?record-no--1")
);
Assert.ok(
await PlacesUtils.history.hasVisits(
"http://foo/bar?record-no--2")
);
Assert.ok(
await PlacesUtils.history.hasVisits(
"http://foo/bar?record-no--3")
);
Assert.ok(
await PlacesUtils.history.hasVisits(
"http://foo/bar?record-no--4")
);
Assert.equal(hmacErrorCount, 1);
_(
"Busting some new server values.");
// Now what happens if we corrupt the HMAC on the server?
for (let i = 5; i < 10; i++) {
let id =
"record-no--" + i;
let modified = 1 + Date.now() / 1000;
let w =
new CryptoWrapper(
"history",
"id");
w.cleartext = {
id,
histUri:
"http://foo/bar?" + id,
title: id,
sortindex: i,
visits: [{ date: (modified - 5) * 1000000, type: visitType }],
deleted:
false,
};
await w.encrypt(Service.collectionKeys.keyForCollection(
"history"));
w.hmac = w.hmac.toUpperCase();
let payload = { ciphertext: w.ciphertext, IV: w.IV, hmac: w.hmac };
history.insert(id, payload, modified);
}
history.timestamp = Date.now() / 1000;
_(
"Server key time hasn't changed.");
Assert.equal(johndoe.modified(
"crypto"), old_key_time);
_(
"Resetting HMAC error timer.");
Service.lastHMACEvent = 0;
_(
"Syncing...");
await sync_and_validate_telem(ping => {
Assert.equal(
ping.engines.find(e => e.name ==
"history").incoming.failed,
5
);
});
_(
"Keys now: " + Service.collectionKeys.keyForCollection(
"history").keyPair
);
_(
"Server keys have been updated, and we skipped over 5 more HMAC errors without adjusting history."
);
Assert.ok(johndoe.modified(
"crypto") > old_key_time);
Assert.equal(hmacErrorCount, 6);
Assert.equal(
false,
await PlacesUtils.history.hasVisits(
"http://foo/bar?record-no--5")
);
Assert.equal(
false,
await PlacesUtils.history.hasVisits(
"http://foo/bar?record-no--6")
);
Assert.equal(
false,
await PlacesUtils.history.hasVisits(
"http://foo/bar?record-no--7")
);
Assert.equal(
false,
await PlacesUtils.history.hasVisits(
"http://foo/bar?record-no--8")
);
Assert.equal(
false,
await PlacesUtils.history.hasVisits(
"http://foo/bar?record-no--9")
);
}
finally {
for (
const pref of Svc.PrefBranch.getChildList(
"")) {
Svc.PrefBranch.clearUserPref(pref);
}
await promiseStopServer(server);
}
});
function run_test() {
Log.repository.rootLogger.addAppender(
new Log.DumpAppender());
validate_all_future_pings();
run_next_test();
}