diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.ts similarity index 78% rename from spec/unit/crypto/backup.spec.js rename to spec/unit/crypto/backup.spec.ts index cab0c0d0d9f..6759fe16152 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.ts @@ -15,6 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { MockedObject } from "jest-mock"; + import '../../olm-loader'; import { logger } from "../../../src/logger"; import * as olmlib from "../../../src/crypto/olmlib"; @@ -22,12 +24,13 @@ import { MatrixClient } from "../../../src/client"; import { MatrixEvent } from "../../../src/models/event"; import * as algorithms from "../../../src/crypto/algorithms"; import { MemoryCryptoStore } from "../../../src/crypto/store/memory-crypto-store"; -import { MockStorageApi } from "../../MockStorageApi"; import * as testUtils from "../../test-utils/test-utils"; import { OlmDevice } from "../../../src/crypto/OlmDevice"; import { Crypto } from "../../../src/crypto"; import { resetCrossSigningKeys } from "./crypto-utils"; import { BackupManager } from "../../../src/crypto/backup"; +import { StubStore } from "../../../src/store/stub"; +import { IAbortablePromise, MatrixScheduler } from '../../../src'; const Olm = global.Olm; @@ -92,8 +95,8 @@ const AES256_KEY_BACKUP_DATA = { }; const CURVE25519_BACKUP_INFO = { - algorithm: "m.megolm_backup.v1.curve25519-aes-sha2", - version: 1, + algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM, + version: '1', auth_data: { public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo", }, @@ -101,7 +104,7 @@ const CURVE25519_BACKUP_INFO = { const AES256_BACKUP_INFO = { algorithm: "org.matrix.msc3270.v1.aes-hmac-sha2", - version: 1, + version: '1', auth_data: { // FIXME: add iv and mac }, @@ -121,21 +124,14 @@ function makeTestClient(cryptoStore) { const scheduler = [ "getQueueForEvent", "queueEvent", "removeEventFromQueue", "setProcessFunction", - ].reduce((r, k) => {r[k] = jest.fn(); return r;}, {}); - const store = [ - "getRoom", "getRooms", "getUser", "getSyncToken", "scrollback", - "save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom", - "storeUser", "getFilterIdByName", "setFilterIdByName", "getFilter", - "storeFilter", "getSyncAccumulator", "startup", "deleteAllData", - ].reduce((r, k) => {r[k] = jest.fn(); return r;}, {}); - store.getSavedSync = jest.fn().mockReturnValue(Promise.resolve(null)); - store.getSavedSyncToken = jest.fn().mockReturnValue(Promise.resolve(null)); - store.setSyncData = jest.fn().mockReturnValue(Promise.resolve(null)); + ].reduce((r, k) => {r[k] = jest.fn(); return r;}, {}) as MockedObject; + const store = new StubStore(); + return new MatrixClient({ baseUrl: "https://my.home.server", idBaseUrl: "https://identity.server", accessToken: "my.access.token", - request: function() {}, // NOP + request: jest.fn(), // NOP store: store, scheduler: scheduler, userId: "@alice:bar", @@ -158,7 +154,6 @@ describe("MegolmBackup", function() { let olmDevice; let mockOlmLib; let mockCrypto; - let mockStorage; let cryptoStore; let megolmDecryption; beforeEach(async function() { @@ -170,8 +165,7 @@ describe("MegolmBackup", function() { ); mockCrypto.backupInfo = CURVE25519_BACKUP_INFO; - mockStorage = new MockStorageApi(); - cryptoStore = new MemoryCryptoStore(mockStorage); + cryptoStore = new MemoryCryptoStore(); olmDevice = new OlmDevice(cryptoStore); @@ -184,7 +178,6 @@ describe("MegolmBackup", function() { describe("backup", function() { let mockBaseApis; - let realSetTimeout; beforeEach(function() { mockBaseApis = {}; @@ -202,14 +195,14 @@ describe("MegolmBackup", function() { // clobber the setTimeout function to run 100x faster. // ideally we would use lolex, but we have no oportunity // to tick the clock between the first try and the retry. - realSetTimeout = global.setTimeout; - global.setTimeout = function(f, n) { + const realSetTimeout = global.setTimeout; + jest.spyOn(global, 'setTimeout').mockImplementation(function(f, n) { return realSetTimeout(f, n/100); - }; + }); }); afterEach(function() { - global.setTimeout = realSetTimeout; + jest.spyOn(global, 'setTimeout').mockRestore(); }); it('automatically calls the key back up', function() { @@ -289,16 +282,16 @@ describe("MegolmBackup", function() { txn); }); }) - .then(() => { - client.enableKeyBackup({ - algorithm: "m.megolm_backup.v1.curve25519-aes-sha2", - version: 1, + .then(async () => { + await client.enableKeyBackup({ + algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM, + version: '1', auth_data: { public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo", }, }); let numCalls = 0; - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { client.http.authedRequest = function( callback, method, path, queryParams, data, opts, ) { @@ -307,17 +300,17 @@ describe("MegolmBackup", function() { if (numCalls >= 2) { // exit out of retry loop if there's something wrong reject(new Error("authedRequest called too many timmes")); - return Promise.resolve({}); + return Promise.resolve({}) as IAbortablePromise; } expect(method).toBe("PUT"); expect(path).toBe("/room_keys/keys"); - expect(queryParams.version).toBe(1); + expect(queryParams.version).toBe('1'); expect(data.rooms[ROOM_ID].sessions).toBeDefined(); expect(data.rooms[ROOM_ID].sessions).toHaveProperty( groupSession.session_id(), ); resolve(); - return Promise.resolve({}); + return Promise.resolve({}) as IAbortablePromise; }; client.crypto.backupManager.backupGroupSession( "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", @@ -371,17 +364,17 @@ describe("MegolmBackup", function() { txn); }); }) - .then(() => { - client.enableKeyBackup({ + .then(async () => { + await client.enableKeyBackup({ algorithm: "org.matrix.msc3270.v1.aes-hmac-sha2", - version: 1, + version: '1', auth_data: { iv: "PsCAtR7gMc4xBd9YS3A9Ow", mac: "ZSDsTFEZK7QzlauCLMleUcX96GQZZM7UNtk4sripSqQ", }, }); let numCalls = 0; - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { client.http.authedRequest = function( callback, method, path, queryParams, data, opts, ) { @@ -390,17 +383,17 @@ describe("MegolmBackup", function() { if (numCalls >= 2) { // exit out of retry loop if there's something wrong reject(new Error("authedRequest called too many timmes")); - return Promise.resolve({}); + return Promise.resolve({}) as IAbortablePromise; } expect(method).toBe("PUT"); expect(path).toBe("/room_keys/keys"); - expect(queryParams.version).toBe(1); + expect(queryParams.version).toBe('1'); expect(data.rooms[ROOM_ID].sessions).toBeDefined(); expect(data.rooms[ROOM_ID].sessions).toHaveProperty( groupSession.session_id(), ); resolve(); - return Promise.resolve({}); + return Promise.resolve({}) as IAbortablePromise; }; client.crypto.backupManager.backupGroupSession( "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", @@ -432,19 +425,12 @@ describe("MegolmBackup", function() { megolmDecryption.olmlib = mockOlmLib; await client.initCrypto(); - let privateKeys; - client.uploadDeviceSigningKeys = async function(e) {return;}; - client.uploadKeySignatures = async function(e) {return;}; - client.on("crossSigning.saveCrossSigningKeys", function(e) { - privateKeys = e; - }); - client.on("crossSigning.getKey", function(e) { - e.done(privateKeys[e.type]); - }); + client.uploadDeviceSigningKeys = async function(e) {return {};}; + client.uploadKeySignatures = async function(e) {return { failures: {} };}; await resetCrossSigningKeys(client); let numCalls = 0; await Promise.all([ - new Promise((resolve, reject) => { + new Promise((resolve, reject) => { let backupInfo; client.http.authedRequest = function( callback, method, path, queryParams, data, opts, @@ -461,24 +447,24 @@ describe("MegolmBackup", function() { ); } catch (e) { reject(e); - return Promise.resolve({}); + return Promise.resolve({}) as IAbortablePromise; } backupInfo = data; - return Promise.resolve({}); + return Promise.resolve({}) as IAbortablePromise; } else if (numCalls === 2) { expect(method).toBe("GET"); expect(path).toBe("/room_keys/version"); resolve(); - return Promise.resolve(backupInfo); + return Promise.resolve(backupInfo) as IAbortablePromise; } else { // exit out of retry loop if there's something wrong reject(new Error("authedRequest called too many times")); - return Promise.resolve({}); + return Promise.resolve({}) as IAbortablePromise; } }; }), client.createKeyBackupVersion({ - algorithm: "m.megolm_backup.v1.curve25519-aes-sha2", + algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM, auth_data: { public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo", }, @@ -488,7 +474,7 @@ describe("MegolmBackup", function() { client.stopClient(); }); - it('retries when a backup fails', function() { + it('retries when a backup fails', async function() { const groupSession = new Olm.OutboundGroupSession(); groupSession.create(); const ibGroupSession = new Olm.InboundGroupSession(); @@ -497,21 +483,13 @@ describe("MegolmBackup", function() { const scheduler = [ "getQueueForEvent", "queueEvent", "removeEventFromQueue", "setProcessFunction", - ].reduce((r, k) => {r[k] = jest.fn(); return r;}, {}); - const store = [ - "getRoom", "getRooms", "getUser", "getSyncToken", "scrollback", - "save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom", - "storeUser", "getFilterIdByName", "setFilterIdByName", "getFilter", - "storeFilter", "getSyncAccumulator", "startup", "deleteAllData", - ].reduce((r, k) => {r[k] = jest.fn(); return r;}, {}); - store.getSavedSync = jest.fn().mockReturnValue(Promise.resolve(null)); - store.getSavedSyncToken = jest.fn().mockReturnValue(Promise.resolve(null)); - store.setSyncData = jest.fn().mockReturnValue(Promise.resolve(null)); + ].reduce((r, k) => {r[k] = jest.fn(); return r;}, {}) as MockedObject; + const store = new StubStore(); const client = new MatrixClient({ baseUrl: "https://my.home.server", idBaseUrl: "https://identity.server", accessToken: "my.access.token", - request: function() {}, // NOP + request: jest.fn(), // NOP store: store, scheduler: scheduler, userId: "@alice:bar", @@ -529,71 +507,68 @@ describe("MegolmBackup", function() { megolmDecryption.olmlib = mockOlmLib; - return client.initCrypto() - .then(() => { - return cryptoStore.doTxn( - "readwrite", - [cryptoStore.STORE_SESSION], - (txn) => { - cryptoStore.addEndToEndInboundGroupSession( - "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", - groupSession.session_id(), - { - forwardingCurve25519KeyChain: undefined, - keysClaimed: { - ed25519: "SENDER_ED25519", - }, - room_id: ROOM_ID, - session: ibGroupSession.pickle(olmDevice.pickleKey), - }, - txn); - }); - }) - .then(() => { - client.enableKeyBackup({ - algorithm: "m.megolm_backup.v1.curve25519-aes-sha2", - version: 1, - auth_data: { - public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo", + await client.initCrypto(); + await cryptoStore.doTxn( + "readwrite", + [cryptoStore.STORE_SESSION], + (txn) => { + cryptoStore.addEndToEndInboundGroupSession( + "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", + groupSession.session_id(), + { + forwardingCurve25519KeyChain: undefined, + keysClaimed: { + ed25519: "SENDER_ED25519", + }, + room_id: ROOM_ID, + session: ibGroupSession.pickle(olmDevice.pickleKey), }, - }); - let numCalls = 0; - return new Promise((resolve, reject) => { - client.http.authedRequest = function( - callback, method, path, queryParams, data, opts, - ) { - ++numCalls; - expect(numCalls).toBeLessThanOrEqual(2); - if (numCalls >= 3) { - // exit out of retry loop if there's something wrong - reject(new Error("authedRequest called too many timmes")); - return Promise.resolve({}); - } - expect(method).toBe("PUT"); - expect(path).toBe("/room_keys/keys"); - expect(queryParams.version).toBe(1); - expect(data.rooms[ROOM_ID].sessions).toBeDefined(); - expect(data.rooms[ROOM_ID].sessions).toHaveProperty( - groupSession.session_id(), - ); - if (numCalls > 1) { - resolve(); - return Promise.resolve({}); - } else { - return Promise.reject( - new Error("this is an expected failure"), - ); - } - }; - client.crypto.backupManager.backupGroupSession( - "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", - groupSession.session_id(), - ); - }).then(() => { - expect(numCalls).toBe(2); - client.stopClient(); - }); + txn); }); + + await client.enableKeyBackup({ + algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM, + version: '1', + auth_data: { + public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo", + }, + }); + let numCalls = 0; + + await new Promise((resolve, reject) => { + client.http.authedRequest = function( + callback, method, path, queryParams, data, opts, + ) { + ++numCalls; + expect(numCalls).toBeLessThanOrEqual(2); + if (numCalls >= 3) { + // exit out of retry loop if there's something wrong + reject(new Error("authedRequest called too many timmes")); + return Promise.resolve({}) as IAbortablePromise; + } + expect(method).toBe("PUT"); + expect(path).toBe("/room_keys/keys"); + expect(queryParams.version).toBe('1'); + expect(data.rooms[ROOM_ID].sessions).toBeDefined(); + expect(data.rooms[ROOM_ID].sessions).toHaveProperty( + groupSession.session_id(), + ); + if (numCalls > 1) { + resolve(); + return Promise.resolve({}) as IAbortablePromise; + } else { + return Promise.reject( + new Error("this is an expected failure"), + ) as IAbortablePromise; + } + }; + return client.crypto.backupManager.backupGroupSession( + "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", + groupSession.session_id(), + ); + }); + expect(numCalls).toBe(2); + client.stopClient(); }); }); diff --git a/spec/unit/crypto/crypto-utils.js b/spec/unit/crypto/crypto-utils.ts similarity index 78% rename from spec/unit/crypto/crypto-utils.js rename to spec/unit/crypto/crypto-utils.ts index ecc6fc4b0ae..3535edaabe7 100644 --- a/spec/unit/crypto/crypto-utils.js +++ b/spec/unit/crypto/crypto-utils.ts @@ -1,11 +1,13 @@ +import { IRecoveryKey } from '../../../src/crypto/api'; +import { CrossSigningLevel } from '../../../src/crypto/CrossSigning'; import { IndexedDBCryptoStore } from '../../../src/crypto/store/indexeddb-crypto-store'; // needs to be phased out and replaced with bootstrapSecretStorage, // but that is doing too much extra stuff for it to be an easy transition. -export async function resetCrossSigningKeys(client, { - level, - authUploadDeviceSigningKeys = async func => await func(), -} = {}) { +export async function resetCrossSigningKeys( + client, + { level }: { level?: CrossSigningLevel} = {}, +): Promise { const crypto = client.crypto; const oldKeys = Object.assign({}, crypto.crossSigningInfo.keys); @@ -30,14 +32,14 @@ export async function resetCrossSigningKeys(client, { await crypto.afterCrossSigningLocalKeyChange(); } -export async function createSecretStorageKey() { +export async function createSecretStorageKey(): Promise { const decryption = new global.Olm.PkDecryption(); const storagePublicKey = decryption.generate_key(); const storagePrivateKey = decryption.get_private_key(); decryption.free(); return { // `pubkey` not used anymore with symmetric 4S - keyInfo: { pubkey: storagePublicKey }, + keyInfo: { pubkey: storagePublicKey, key: undefined }, privateKey: storagePrivateKey, }; }