From 4866da9299b473fe61431cbce4893525a4351f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Cla=C3=9Fen?= Date: Thu, 26 Jan 2023 09:31:08 +0100 Subject: [PATCH] Fixed: caching keys didn't work since chrome.local doesn't support binary data; instead use IndexedDB for storing them. --- packages/anonymous-communication/src/index.js | 6 +- .../src/server-public-key-accessor.js | 12 ++-- .../test/helpers/in-memory-database.js | 43 +++++++++++ .../test/helpers/memory-map.js | 72 ------------------- .../test/server-public-key-accessor.spec.js | 31 +++++--- 5 files changed, 75 insertions(+), 89 deletions(-) create mode 100644 packages/anonymous-communication/test/helpers/in-memory-database.js delete mode 100644 packages/anonymous-communication/test/helpers/memory-map.js diff --git a/packages/anonymous-communication/src/index.js b/packages/anonymous-communication/src/index.js index 0625beff..d1933555 100644 --- a/packages/anonymous-communication/src/index.js +++ b/packages/anonymous-communication/src/index.js @@ -16,11 +16,11 @@ import { InvalidMessageError } from './errors.js'; import { TrustedClock } from './trusted-clock.js'; export default class AnonymousCommunication { - constructor({ config, storage }) { + constructor({ config, connectDatabase }) { + this.cacheDatabase = connectDatabase('cache'); this.serverPublicKeyAccessor = new ServerPublicKeyAccessor({ config, - storage, - storageKey: 'wtm.anonymous-communication.server-ecdh-keys', + database: this.cacheDatabase, }); this.config = config; if (!config.CHANNEL) { diff --git a/packages/anonymous-communication/src/server-public-key-accessor.js b/packages/anonymous-communication/src/server-public-key-accessor.js index 441d156b..b7843f52 100644 --- a/packages/anonymous-communication/src/server-public-key-accessor.js +++ b/packages/anonymous-communication/src/server-public-key-accessor.js @@ -18,12 +18,12 @@ function isYYYYMMDD(date) { } export default class ServerPublicKeyAccessor { - constructor({ config, storage, storageKey }) { + constructor({ config, database }) { // Note: do not go through proxies when fetching keys; otherwise, // the proxy could replace it, and the key exchange would be insecure. this.collectorUrl = config.COLLECTOR_DIRECT_URL; - this.storage = storage; - this.storageKey = storageKey; + this.database = database; + this.storageKey = 'server-ecdh-keys'; this._knownKeys = new Map(); } @@ -51,7 +51,7 @@ export default class ServerPublicKeyAccessor { // try to load from disk let knownKeys; try { - const keysFromDisk = await this.storage + const keysFromDisk = await this.database .get(this.storageKey) .catch(() => null); if (keysFromDisk && keysFromDisk.some(([date]) => date === today)) { @@ -94,8 +94,8 @@ export default class ServerPublicKeyAccessor { // update disk cache try { - const entry = [...knownKeys].map((date, { key }) => [date, key]); - await this.storage.set(this.storageKey, entry); + const entry = [...knownKeys].map(([date, { key }]) => [date, key]); + await this.database.set(this.storageKey, entry); } catch (e) { logger.warn('Failed to cache server keys to disk.', e); } diff --git a/packages/anonymous-communication/test/helpers/in-memory-database.js b/packages/anonymous-communication/test/helpers/in-memory-database.js new file mode 100644 index 00000000..eee7c553 --- /dev/null +++ b/packages/anonymous-communication/test/helpers/in-memory-database.js @@ -0,0 +1,43 @@ +/** + * WhoTracks.Me + * https://whotracks.me/ + * + * Copyright 2017-present Ghostery GmbH. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +/** + * Implements the IndexedDBKeyValueStore interface. + */ +export default class InMemoryDatabase { + constructor() { + this.db = new Map(); + } + + async open() {} + + async close() {} + + async get(key) { + return this.db.get(key); + } + + async set(key, value) { + this.db.set(key, value); + } + + async remove(key) { + return this.db.delete(key); + } + + async clear() { + this.db.clear(); + } + + async keys() { + return [...this.db.keys()]; + } +} diff --git a/packages/anonymous-communication/test/helpers/memory-map.js b/packages/anonymous-communication/test/helpers/memory-map.js deleted file mode 100644 index 0bd740de..00000000 --- a/packages/anonymous-communication/test/helpers/memory-map.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * WhoTracks.Me - * https://whotracks.me/ - * - * Copyright 2017-present Ghostery GmbH. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -export default class MemoryPersistentMap { - constructor() { - this.db = new Map(); - } - - init() { - return Promise.resolve(); - } - - unload() { - this.db.clear(); - } - - async destroy() { - this.db.clear(); - } - - async get(key) { - return this.db.get(key); - } - - async set(key, value) { - this.db.set(key, value); - } - - async bulkSetFromMap(map) { - map.forEach((value, key) => this.db.set(key, value)); - } - - async has(key) { - return this.db.has(key); - } - - async delete(key) { - this.db.delete(key); - } - - async bulkDelete(keys) { - keys.forEach((key) => this.db.delete(key)); - } - - async clear() { - this.db.clear(); - } - - async size() { - return this.db.size; - } - - async keys() { - return [...this.db.keys()]; - } - - async values() { - return [...this.db.values()]; - } - - async entries() { - return [...this.db.entries()]; - } -} diff --git a/packages/anonymous-communication/test/server-public-key-accessor.spec.js b/packages/anonymous-communication/test/server-public-key-accessor.spec.js index 8c511738..917209d0 100644 --- a/packages/anonymous-communication/test/server-public-key-accessor.spec.js +++ b/packages/anonymous-communication/test/server-public-key-accessor.spec.js @@ -13,7 +13,7 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { Buffer } from 'buffer'; -import MemoryPersistentMap from './helpers/memory-map.js'; +import InMemoryDatabase from './helpers/in-memory-database.js'; import ServerPublicKeyAccessor from '../src/server-public-key-accessor.js'; import logger from '../src/logger.js'; @@ -52,28 +52,25 @@ const MOCKS = { }; describe('#ServerPublicKeyAccessor', function () { - let storage; + let database; let uut; - const someStorageKey = 'test-storage-key'; const assumeKeysOnDisk = async (storedKeys) => { const entry = storedKeys.map(({ date, key }) => [ date, Buffer.from(key, 'base64'), ]); - await storage.set(someStorageKey, entry); + await database.set(uut.storageKey, entry); }; beforeEach(async function () { - // in-memory implementation of storage - storage = new MemoryPersistentMap(); + database = new InMemoryDatabase(); const config = { COLLECTOR_DIRECT_URL: '192.0.2.0', // TEST-NET-1 address }; uut = new ServerPublicKeyAccessor({ config, - storage, - storageKey: someStorageKey, + database, }); MOCKS.reset(); sinon.stub(window, 'fetch').callsFake(MOCKS.fetch); @@ -117,4 +114,22 @@ describe('#ServerPublicKeyAccessor', function () { }); expect(MOCKS.fetch._numCalls).to.equal(0); }); + + it('should fetch newer keys if the cached ones are outdated', async function () { + const oneYearAgo = (MOCKS.today - '00010000').toString(); + await assumeKeysOnDisk([{ date: oneYearAgo, key: MOCKS.fakeKey }]); + expect(await uut.getKey(MOCKS.today)).to.deep.equal({ + date: MOCKS.today, + publicKey: MOCKS.fakeImportedKey, + }); + expect(MOCKS.fetch._numCalls).to.equal(1); + + // also check that the new keys were properly cached + MOCKS.fetch._numCalls = 0; + expect(await uut.getKey(MOCKS.today)).to.deep.equal({ + date: MOCKS.today, + publicKey: MOCKS.fakeImportedKey, + }); + expect(MOCKS.fetch._numCalls).to.equal(0); + }); });