diff --git a/spec/integ/megolm-integ.spec.ts b/spec/integ/megolm-integ.spec.ts index 9454749a9..6c42ccde4 100644 --- a/spec/integ/megolm-integ.spec.ts +++ b/spec/integ/megolm-integ.spec.ts @@ -1133,10 +1133,10 @@ describe("megolm", () => { 'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { - beccaTestClient.client.crypto!.cryptoStore.getAccount(txn, (pickledAccount: string) => { + beccaTestClient.client.crypto!.cryptoStore.getAccount(txn, (pickledAccount: string | null) => { const account = new global.Olm.Account(); try { - account.unpickle(beccaTestClient.client.crypto!.olmDevice.pickleKey, pickledAccount); + account.unpickle(beccaTestClient.client.crypto!.olmDevice.pickleKey, pickledAccount!); p2pSession.create_outbound(account, aliceTestClient.getDeviceKey(), aliceOtk.key); } finally { account.free(); @@ -1271,10 +1271,10 @@ describe("megolm", () => { 'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { - beccaTestClient.client.crypto!.cryptoStore.getAccount(txn, (pickledAccount: string) => { + beccaTestClient.client.crypto!.cryptoStore.getAccount(txn, (pickledAccount: string | null) => { const account = new global.Olm.Account(); try { - account.unpickle(beccaTestClient.client.crypto!.olmDevice.pickleKey, pickledAccount); + account.unpickle(beccaTestClient.client.crypto!.olmDevice.pickleKey, pickledAccount!); p2pSession.create_outbound(account, aliceTestClient.getDeviceKey(), aliceOtk.key); } finally { account.free(); diff --git a/spec/olm-loader.ts b/spec/olm-loader.ts index 388220f0d..3ab07254d 100644 --- a/spec/olm-loader.ts +++ b/spec/olm-loader.ts @@ -16,7 +16,6 @@ limitations under the License. */ import { logger } from '../src/logger'; -import * as utils from "../src/utils"; // try to load the olm library. try { @@ -26,12 +25,3 @@ try { } catch (e) { logger.warn("unable to run crypto tests: libolm not available"); } - -// also try to set node crypto -try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const crypto = require('crypto'); - utils.setCrypto(crypto); -} catch (err) { - logger.log('nodejs was compiled without crypto support: some tests will fail'); -} diff --git a/spec/unit/crypto/secrets.spec.ts b/spec/unit/crypto/secrets.spec.ts index 7692292ff..ddac48cff 100644 --- a/spec/unit/crypto/secrets.spec.ts +++ b/spec/unit/crypto/secrets.spec.ts @@ -23,19 +23,10 @@ import { makeTestClients } from './verification/util'; import { encryptAES } from "../../../src/crypto/aes"; import { resetCrossSigningKeys, createSecretStorageKey } from "./crypto-utils"; import { logger } from '../../../src/logger'; -import * as utils from "../../../src/utils"; import { ICreateClientOpts } from '../../../src/client'; import { ISecretStorageKeyInfo } from '../../../src/crypto/api'; import { DeviceInfo } from '../../../src/crypto/deviceinfo'; -try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const crypto = require('crypto'); - utils.setCrypto(crypto); -} catch (err) { - logger.log('nodejs was compiled without crypto support'); -} - async function makeTestClient(userInfo: { userId: string, deviceId: string}, options: Partial = {}) { const client = (new TestClient( userInfo.userId, userInfo.deviceId, undefined, undefined, options, diff --git a/src/crypto/CrossSigning.ts b/src/crypto/CrossSigning.ts index 4e5c9f452..390720988 100644 --- a/src/crypto/CrossSigning.ts +++ b/src/crypto/CrossSigning.ts @@ -31,7 +31,7 @@ import { ICrossSigningKey, ISignedKey, MatrixClient } from "../client"; import { OlmDevice } from "./OlmDevice"; import { ICryptoCallbacks } from "../matrix"; import { ISignatures } from "../@types/signed"; -import { CryptoStore } from "./store/base"; +import { CryptoStore, SecretStorePrivateKeys } from "./store/base"; import { ISecretStorageKeyInfo } from "./api"; const KEY_REQUEST_TIMEOUT_MS = 1000 * 60; @@ -699,7 +699,10 @@ export class DeviceTrustLevel { export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: OlmDevice): ICacheCallbacks { return { - getCrossSigningKeyCache: async function(type: string, _expectedPublicKey: string): Promise { + getCrossSigningKeyCache: async function( + type: keyof SecretStorePrivateKeys, + _expectedPublicKey: string, + ): Promise { const key = await new Promise((resolve) => { return store.doTxn( 'readonly', @@ -718,7 +721,10 @@ export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: O return key; } }, - storeCrossSigningKeyCache: async function(type: string, key: Uint8Array): Promise { + storeCrossSigningKeyCache: async function( + type: keyof SecretStorePrivateKeys, + key: Uint8Array, + ): Promise { if (!(key instanceof Uint8Array)) { throw new Error( `storeCrossSigningKeyCache expects Uint8Array, got ${key}`, diff --git a/src/crypto/OlmDevice.ts b/src/crypto/OlmDevice.ts index cca3b7db9..8cd7b3552 100644 --- a/src/crypto/OlmDevice.ts +++ b/src/crypto/OlmDevice.ts @@ -308,10 +308,10 @@ export class OlmDevice { * @private */ private getAccount(txn: unknown, func: (account: Account) => void): void { - this.cryptoStore.getAccount(txn, (pickledAccount: string) => { + this.cryptoStore.getAccount(txn, (pickledAccount: string | null) => { const account = new global.Olm.Account(); try { - account.unpickle(this.pickleKey, pickledAccount); + account.unpickle(this.pickleKey, pickledAccount!); func(account); } finally { account.free(); @@ -350,8 +350,8 @@ export class OlmDevice { IndexedDBCryptoStore.STORE_SESSIONS, ], (txn) => { - this.cryptoStore.getAccount(txn, (pickledAccount: string) => { - result.pickledAccount = pickledAccount; + this.cryptoStore.getAccount(txn, (pickledAccount: string | null) => { + result.pickledAccount = pickledAccount!; }); result.sessions = []; // Note that the pickledSession object we get in the callback diff --git a/src/crypto/aes.ts b/src/crypto/aes.ts index 4aa38ac54..33971e634 100644 --- a/src/crypto/aes.ts +++ b/src/crypto/aes.ts @@ -14,129 +14,39 @@ See the License for the specific language governing permissions and limitations under the License. */ -import type { BinaryLike } from "crypto"; -import { getCrypto } from '../utils'; import { decodeBase64, encodeBase64 } from './olmlib'; - -const subtleCrypto = (typeof window !== "undefined" && window.crypto) ? - (window.crypto.subtle || window.crypto.webkitSubtle) : null; +import { subtleCrypto, crypto, TextEncoder } from "./crypto"; // salt for HKDF, with 8 bytes of zeros const zeroSalt = new Uint8Array(8); export interface IEncryptedPayload { [key: string]: any; // extensible - iv?: string; - ciphertext?: string; - mac?: string; + iv: string; + ciphertext: string; + mac: string; } /** - * encrypt a string in Node.js + * encrypt a string * * @param {string} data the plaintext to encrypt * @param {Uint8Array} key the encryption key to use * @param {string} name the name of the secret * @param {string} ivStr the initialization vector to use */ -async function encryptNode(data: string, key: Uint8Array, name: string, ivStr?: string): Promise { - const crypto = getCrypto(); - if (!crypto) { - throw new Error("No usable crypto implementation"); - } - - let iv; - if (ivStr) { - iv = decodeBase64(ivStr); - } else { - iv = crypto.randomBytes(16); - - // clear bit 63 of the IV to stop us hitting the 64-bit counter boundary - // (which would mean we wouldn't be able to decrypt on Android). The loss - // of a single bit of iv is a price we have to pay. - iv[8] &= 0x7f; - } - - const [aesKey, hmacKey] = deriveKeysNode(key, name); - - const cipher = crypto.createCipheriv("aes-256-ctr", aesKey, iv); - const ciphertext = Buffer.concat([ - cipher.update(data, "utf8"), - cipher.final(), - ]); - - const hmac = crypto.createHmac("sha256", hmacKey) - .update(ciphertext).digest("base64"); - - return { - iv: encodeBase64(iv), - ciphertext: ciphertext.toString("base64"), - mac: hmac, - }; -} - -/** - * decrypt a string in Node.js - * - * @param {object} data the encrypted data - * @param {string} data.ciphertext the ciphertext in base64 - * @param {string} data.iv the initialization vector in base64 - * @param {string} data.mac the HMAC in base64 - * @param {Uint8Array} key the encryption key to use - * @param {string} name the name of the secret - */ -async function decryptNode(data: IEncryptedPayload, key: Uint8Array, name: string): Promise { - const crypto = getCrypto(); - if (!crypto) { - throw new Error("No usable crypto implementation"); - } - - const [aesKey, hmacKey] = deriveKeysNode(key, name); - - const hmac = crypto.createHmac("sha256", hmacKey) - .update(Buffer.from(data.ciphertext, "base64")) - .digest("base64").replace(/=+$/g, ''); - - if (hmac !== data.mac.replace(/=+$/g, '')) { - throw new Error(`Error decrypting secret ${name}: bad MAC`); - } - - const decipher = crypto.createDecipheriv( - "aes-256-ctr", aesKey, decodeBase64(data.iv), - ); - return decipher.update(data.ciphertext, "base64", "utf8") - + decipher.final("utf8"); -} - -function deriveKeysNode(key: BinaryLike, name: string): [Buffer, Buffer] { - const crypto = getCrypto(); - const prk = crypto.createHmac("sha256", zeroSalt).update(key).digest(); - - const b = Buffer.alloc(1, 1); - const aesKey = crypto.createHmac("sha256", prk) - .update(name, "utf8").update(b).digest(); - b[0] = 2; - const hmacKey = crypto.createHmac("sha256", prk) - .update(aesKey).update(name, "utf8").update(b).digest(); - - return [aesKey, hmacKey]; -} - -/** - * encrypt a string in Node.js - * - * @param {string} data the plaintext to encrypt - * @param {Uint8Array} key the encryption key to use - * @param {string} name the name of the secret - * @param {string} ivStr the initialization vector to use - */ -async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr?: string): Promise { - let iv; +export async function encryptAES( + data: string, + key: Uint8Array, + name: string, + ivStr?: string, +): Promise { + let iv: Uint8Array; if (ivStr) { iv = decodeBase64(ivStr); } else { iv = new Uint8Array(16); - window.crypto.getRandomValues(iv); + crypto.getRandomValues(iv); // clear bit 63 of the IV to stop us hitting the 64-bit counter boundary // (which would mean we wouldn't be able to decrypt on Android). The loss @@ -144,7 +54,7 @@ async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr iv[8] &= 0x7f; } - const [aesKey, hmacKey] = await deriveKeysBrowser(key, name); + const [aesKey, hmacKey] = await deriveKeys(key, name); const encodedData = new TextEncoder().encode(data); const ciphertext = await subtleCrypto.encrypt( @@ -171,7 +81,7 @@ async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr } /** - * decrypt a string in the browser + * decrypt a string * * @param {object} data the encrypted data * @param {string} data.ciphertext the ciphertext in base64 @@ -180,8 +90,8 @@ async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr * @param {Uint8Array} key the encryption key to use * @param {string} name the name of the secret */ -async function decryptBrowser(data: IEncryptedPayload, key: Uint8Array, name: string): Promise { - const [aesKey, hmacKey] = await deriveKeysBrowser(key, name); +export async function decryptAES(data: IEncryptedPayload, key: Uint8Array, name: string): Promise { + const [aesKey, hmacKey] = await deriveKeys(key, name); const ciphertext = decodeBase64(data.ciphertext); @@ -207,7 +117,7 @@ async function decryptBrowser(data: IEncryptedPayload, key: Uint8Array, name: st return new TextDecoder().decode(new Uint8Array(plaintext)); } -async function deriveKeysBrowser(key: Uint8Array, name: string): Promise<[CryptoKey, CryptoKey]> { +async function deriveKeys(key: Uint8Array, name: string): Promise<[CryptoKey, CryptoKey]> { const hkdfkey = await subtleCrypto.importKey( 'raw', key, @@ -253,14 +163,6 @@ async function deriveKeysBrowser(key: Uint8Array, name: string): Promise<[Crypto return Promise.all([aesProm, hmacProm]); } -export function encryptAES(data: string, key: Uint8Array, name: string, ivStr?: string): Promise { - return subtleCrypto ? encryptBrowser(data, key, name, ivStr) : encryptNode(data, key, name, ivStr); -} - -export function decryptAES(data: IEncryptedPayload, key: Uint8Array, name: string): Promise { - return subtleCrypto ? decryptBrowser(data, key, name) : decryptNode(data, key, name); -} - // string of zeroes, for calculating the key check const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; diff --git a/src/crypto/backup.ts b/src/crypto/backup.ts index c6c4d66e2..6722aa363 100644 --- a/src/crypto/backup.ts +++ b/src/crypto/backup.ts @@ -26,13 +26,20 @@ import { MEGOLM_ALGORITHM, verifySignature } from "./olmlib"; import { DeviceInfo } from "./deviceinfo"; import { DeviceTrustLevel } from './CrossSigning'; import { keyFromPassphrase } from './key_passphrase'; -import { getCrypto, sleep } from "../utils"; +import { sleep } from "../utils"; import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; import { encodeRecoveryKey } from './recoverykey'; -import { calculateKeyCheck, decryptAES, encryptAES } from './aes'; -import { IAes256AuthData, ICurve25519AuthData, IKeyBackupInfo, IKeyBackupSession } from "./keybackup"; +import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from './aes'; +import { + Curve25519SessionData, + IAes256AuthData, + ICurve25519AuthData, + IKeyBackupInfo, + IKeyBackupSession, +} from "./keybackup"; import { UnstableValue } from "../NamespacedValue"; import { CryptoEvent, IMegolmSessionData } from "./index"; +import { crypto } from "./crypto"; const KEY_BACKUP_KEYS_PER_REQUEST = 200; const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms @@ -677,7 +684,9 @@ export class Curve25519 implements BackupAlgorithm { return this.publicKey.encrypt(JSON.stringify(plainText)); } - public async decryptSessions(sessions: Record): Promise { + public async decryptSessions( + sessions: Record>, + ): Promise { const privKey = await this.getKey(); const decryption = new global.Olm.PkDecryption(); try { @@ -711,7 +720,7 @@ export class Curve25519 implements BackupAlgorithm { public async keyMatches(key: Uint8Array): Promise { const decryption = new global.Olm.PkDecryption(); - let pubKey; + let pubKey: string; try { pubKey = decryption.init_with_private_key(key); } finally { @@ -727,18 +736,9 @@ export class Curve25519 implements BackupAlgorithm { } function randomBytes(size: number): Uint8Array { - const crypto: {randomBytes: (n: number) => Uint8Array} | undefined = getCrypto() as any; - if (crypto) { - // nodejs version - return crypto.randomBytes(size); - } - if (window?.crypto) { - // browser version - const buf = new Uint8Array(size); - window.crypto.getRandomValues(buf); - return buf; - } - throw new Error("No usable crypto implementation"); + const buf = new Uint8Array(size); + crypto.getRandomValues(buf); + return buf; } const UNSTABLE_MSC3270_NAME = new UnstableValue(null, "org.matrix.msc3270.v1.aes-hmac-sha2"); @@ -807,7 +807,9 @@ export class Aes256 implements BackupAlgorithm { return encryptAES(JSON.stringify(plainText), this.key, data.session_id); } - public async decryptSessions(sessions: Record): Promise { + public async decryptSessions( + sessions: Record>, + ): Promise { const keys: IMegolmSessionData[] = []; for (const [sessionId, sessionData] of Object.entries(sessions)) { diff --git a/src/crypto/crypto.ts b/src/crypto/crypto.ts new file mode 100644 index 000000000..eb8de60e3 --- /dev/null +++ b/src/crypto/crypto.ts @@ -0,0 +1,31 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export let crypto = global.window?.crypto; +export let subtleCrypto = global.window?.crypto?.subtle ?? global.window?.crypto?.webkitSubtle; +export let TextEncoder = global.window?.TextEncoder; + +/* eslint-disable @typescript-eslint/no-var-requires */ +if (!crypto) { + crypto = require("crypto").webcrypto; +} +if (!subtleCrypto) { + subtleCrypto = crypto?.subtle; +} +if (!TextEncoder) { + TextEncoder = require("util").TextEncoder; +} +/* eslint-enable @typescript-eslint/no-var-requires */ diff --git a/src/crypto/key_passphrase.ts b/src/crypto/key_passphrase.ts index 169eb3562..b838fae85 100644 --- a/src/crypto/key_passphrase.ts +++ b/src/crypto/key_passphrase.ts @@ -15,10 +15,7 @@ limitations under the License. */ import { randomString } from '../randomstring'; -import { getCrypto } from '../utils'; - -const subtleCrypto = (typeof window !== "undefined" && window.crypto) ? - (window.crypto.subtle || window.crypto.webkitSubtle) : null; +import { subtleCrypto, TextEncoder } from "./crypto"; const DEFAULT_ITERATIONS = 500000; @@ -75,21 +72,8 @@ export async function deriveKey( iterations: number, numBits = DEFAULT_BITSIZE, ): Promise { - return subtleCrypto - ? deriveKeyBrowser(password, salt, iterations, numBits) - : deriveKeyNode(password, salt, iterations, numBits); -} - -async function deriveKeyBrowser( - password: string, - salt: string, - iterations: number, - numBits: number, -): Promise { - const subtleCrypto = global.crypto.subtle; - const TextEncoder = global.TextEncoder; if (!subtleCrypto || !TextEncoder) { - throw new Error("Password-based backup is not avaiable on this platform"); + throw new Error("Password-based backup is not available on this platform"); } const key = await subtleCrypto.importKey( @@ -113,17 +97,3 @@ async function deriveKeyBrowser( return new Uint8Array(keybits); } - -async function deriveKeyNode( - password: string, - salt: string, - iterations: number, - numBits: number, -): Promise { - const crypto = getCrypto(); - if (!crypto) { - throw new Error("No usable crypto implementation"); - } - - return crypto.pbkdf2Sync(password, Buffer.from(salt, 'binary'), iterations, numBits, 'sha512'); -} diff --git a/src/crypto/keybackup.ts b/src/crypto/keybackup.ts index 161351632..919266a3e 100644 --- a/src/crypto/keybackup.ts +++ b/src/crypto/keybackup.ts @@ -23,18 +23,18 @@ export interface Curve25519SessionData { mac: string; } -export interface IKeyBackupSession { - first_message_index: number; // eslint-disable-line camelcase - forwarded_count: number; // eslint-disable-line camelcase - is_verified: boolean; // eslint-disable-line camelcase - session_data: Curve25519SessionData | IEncryptedPayload; // eslint-disable-line camelcase +/* eslint-disable camelcase */ +export interface IKeyBackupSession { + first_message_index: number; + forwarded_count: number; + is_verified: boolean; + session_data: T; } export interface IKeyBackupRoomSessions { [sessionId: string]: IKeyBackupSession; } -/* eslint-disable camelcase */ export interface ICurve25519AuthData { public_key: string; private_key_salt?: string; diff --git a/src/crypto/store/base.ts b/src/crypto/store/base.ts index 6b938aadf..d266d3dca 100644 --- a/src/crypto/store/base.ts +++ b/src/crypto/store/base.ts @@ -24,8 +24,9 @@ import { IDevice } from "../deviceinfo"; import { ICrossSigningInfo } from "../CrossSigning"; import { PrefixedLogger } from "../../logger"; import { InboundGroupSessionData } from "../OlmDevice"; -import { IEncryptedPayload } from "../aes"; import { MatrixEvent } from "../../models/event"; +import { DehydrationManager } from "../dehydration"; +import { IEncryptedPayload } from "../aes"; /** * Internal module. Definitions for storage for the crypto module @@ -33,6 +34,16 @@ import { MatrixEvent } from "../../models/event"; * @module */ +export interface SecretStorePrivateKeys { + dehydration: { + keyInfo: DehydrationManager["keyInfo"]; + key: IEncryptedPayload; + deviceDisplayName: string; + time: number; + } | null; + "m.megolm_backup.v1": IEncryptedPayload; +} + /** * Abstraction of things that can store data required for end-to-end encryption * @@ -58,12 +69,20 @@ export interface CryptoStore { deleteOutgoingRoomKeyRequest(requestId: string, expectedState: number): Promise; // Olm Account - getAccount(txn: unknown, func: (accountPickle: string) => void); + getAccount(txn: unknown, func: (accountPickle: string | null) => void); storeAccount(txn: unknown, accountPickle: string): void; - getCrossSigningKeys(txn: unknown, func: (keys: Record) => void): void; - getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => void, type: string): void; + getCrossSigningKeys(txn: unknown, func: (keys: Record | null) => void): void; + getSecretStorePrivateKey( + txn: unknown, + func: (key: SecretStorePrivateKeys[K] | null) => void, + type: K, + ): void; storeCrossSigningKeys(txn: unknown, keys: Record): void; - storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void; + storeSecretStorePrivateKey( + txn: unknown, + type: K, + key: SecretStorePrivateKeys[K], + ): void; // Olm Sessions countEndToEndSessions(txn: unknown, func: (count: number) => void): void; diff --git a/src/crypto/store/indexeddb-crypto-store-backend.ts b/src/crypto/store/indexeddb-crypto-store-backend.ts index 51cd62a1e..45de1d604 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.ts +++ b/src/crypto/store/indexeddb-crypto-store-backend.ts @@ -25,14 +25,13 @@ import { IWithheld, Mode, OutgoingRoomKeyRequest, - ParkedSharedHistory, + ParkedSharedHistory, SecretStorePrivateKeys, } from "./base"; import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index"; import { ICrossSigningKey } from "../../client"; import { IOlmDevice } from "../algorithms/megolm"; import { IRoomEncryption } from "../RoomList"; import { InboundGroupSessionData } from "../OlmDevice"; -import { IEncryptedPayload } from "../aes"; const PROFILE_TRANSACTIONS = false; @@ -369,7 +368,7 @@ export class Backend implements CryptoStore { // Olm Account - public getAccount(txn: IDBTransaction, func: (accountPickle: string) => void): void { + public getAccount(txn: IDBTransaction, func: (accountPickle: string | null) => void): void { const objectStore = txn.objectStore("account"); const getReq = objectStore.get("-"); getReq.onsuccess = function() { @@ -386,7 +385,10 @@ export class Backend implements CryptoStore { objectStore.put(accountPickle, "-"); } - public getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record) => void): void { + public getCrossSigningKeys( + txn: IDBTransaction, + func: (keys: Record | null) => void, + ): void { const objectStore = txn.objectStore("account"); const getReq = objectStore.get("crossSigningKeys"); getReq.onsuccess = function() { @@ -398,10 +400,10 @@ export class Backend implements CryptoStore { }; } - public getSecretStorePrivateKey( + public getSecretStorePrivateKey( txn: IDBTransaction, - func: (key: IEncryptedPayload | null) => void, - type: string, + func: (key: SecretStorePrivateKeys[K] | null) => void, + type: K, ): void { const objectStore = txn.objectStore("account"); const getReq = objectStore.get(`ssss_cache:${type}`); @@ -419,7 +421,11 @@ export class Backend implements CryptoStore { objectStore.put(keys, "crossSigningKeys"); } - public storeSecretStorePrivateKey(txn: IDBTransaction, type: string, key: IEncryptedPayload): void { + public storeSecretStorePrivateKey( + txn: IDBTransaction, + type: K, + key: SecretStorePrivateKeys[K], + ): void { const objectStore = txn.objectStore("account"); objectStore.put(key, `ssss_cache:${type}`); } diff --git a/src/crypto/store/indexeddb-crypto-store.ts b/src/crypto/store/indexeddb-crypto-store.ts index 21f704fd8..ad29510f9 100644 --- a/src/crypto/store/indexeddb-crypto-store.ts +++ b/src/crypto/store/indexeddb-crypto-store.ts @@ -29,14 +29,13 @@ import { IWithheld, Mode, OutgoingRoomKeyRequest, - ParkedSharedHistory, + ParkedSharedHistory, SecretStorePrivateKeys, } from "./base"; import { IRoomKeyRequestBody } from "../index"; import { ICrossSigningKey } from "../../client"; import { IOlmDevice } from "../algorithms/megolm"; import { IRoomEncryption } from "../RoomList"; import { InboundGroupSessionData } from "../OlmDevice"; -import { IEncryptedPayload } from "../aes"; /** * Internal module. indexeddb storage for e2e. @@ -323,7 +322,7 @@ export class IndexedDBCryptoStore implements CryptoStore { * @param {*} txn An active transaction. See doTxn(). * @param {function(string)} func Called with the account pickle */ - public getAccount(txn: IDBTransaction, func: (accountPickle: string) => void) { + public getAccount(txn: IDBTransaction, func: (accountPickle: string | null) => void) { this.backend.getAccount(txn, func); } @@ -346,7 +345,10 @@ export class IndexedDBCryptoStore implements CryptoStore { * @param {function(string)} func Called with the account keys object: * { key_type: base64 encoded seed } where key type = user_signing_key_seed or self_signing_key_seed */ - public getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record) => void): void { + public getCrossSigningKeys( + txn: IDBTransaction, + func: (keys: Record | null) => void, + ): void { this.backend.getCrossSigningKeys(txn, func); } @@ -355,10 +357,10 @@ export class IndexedDBCryptoStore implements CryptoStore { * @param {function(string)} func Called with the private key * @param {string} type A key type */ - public getSecretStorePrivateKey( + public getSecretStorePrivateKey( txn: IDBTransaction, - func: (key: IEncryptedPayload | null) => void, - type: string, + func: (key: SecretStorePrivateKeys[K] | null) => void, + type: K, ): void { this.backend.getSecretStorePrivateKey(txn, func, type); } @@ -380,7 +382,11 @@ export class IndexedDBCryptoStore implements CryptoStore { * @param {string} type The type of cross-signing private key to store * @param {string} key keys object as getCrossSigningKeys() */ - public storeSecretStorePrivateKey(txn: IDBTransaction, type: string, key: IEncryptedPayload): void { + public storeSecretStorePrivateKey( + txn: IDBTransaction, + type: K, + key: SecretStorePrivateKeys[K], + ): void { this.backend.storeSecretStorePrivateKey(txn, type, key); } diff --git a/src/crypto/store/localStorage-crypto-store.ts b/src/crypto/store/localStorage-crypto-store.ts index e9e0f99ca..896dff8f1 100644 --- a/src/crypto/store/localStorage-crypto-store.ts +++ b/src/crypto/store/localStorage-crypto-store.ts @@ -16,12 +16,11 @@ limitations under the License. import { logger } from '../../logger'; import { MemoryCryptoStore } from './memory-crypto-store'; -import { IDeviceData, IProblem, ISession, ISessionInfo, IWithheld, Mode } from "./base"; +import { IDeviceData, IProblem, ISession, ISessionInfo, IWithheld, Mode, SecretStorePrivateKeys } from "./base"; import { IOlmDevice } from "../algorithms/megolm"; import { IRoomEncryption } from "../RoomList"; import { ICrossSigningKey } from "../../client"; import { InboundGroupSessionData } from "../OlmDevice"; -import { IEncryptedPayload } from "../aes"; /** * Internal module. Partial localStorage backed storage for e2e. @@ -374,7 +373,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { // Olm account - public getAccount(txn: unknown, func: (accountPickle: string) => void): void { + public getAccount(txn: unknown, func: (accountPickle: string | null) => void): void { const accountPickle = getJsonItem(this.store, KEY_END_TO_END_ACCOUNT); func(accountPickle); } @@ -383,13 +382,17 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { setJsonItem(this.store, KEY_END_TO_END_ACCOUNT, accountPickle); } - public getCrossSigningKeys(txn: unknown, func: (keys: Record) => void): void { + public getCrossSigningKeys(txn: unknown, func: (keys: Record | null) => void): void { const keys = getJsonItem>(this.store, KEY_CROSS_SIGNING_KEYS); func(keys); } - public getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => void, type: string): void { - const key = getJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`); + public getSecretStorePrivateKey( + txn: unknown, + func: (key: SecretStorePrivateKeys[K] | null) => void, + type: K, + ): void { + const key = getJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`); func(key); } @@ -397,7 +400,11 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { setJsonItem(this.store, KEY_CROSS_SIGNING_KEYS, keys); } - public storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void { + public storeSecretStorePrivateKey( + txn: unknown, + type: K, + key: SecretStorePrivateKeys[K], + ): void { setJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`, key); } diff --git a/src/crypto/store/memory-crypto-store.ts b/src/crypto/store/memory-crypto-store.ts index a0195bb44..8dc9a772e 100644 --- a/src/crypto/store/memory-crypto-store.ts +++ b/src/crypto/store/memory-crypto-store.ts @@ -25,14 +25,13 @@ import { IWithheld, Mode, OutgoingRoomKeyRequest, - ParkedSharedHistory, + ParkedSharedHistory, SecretStorePrivateKeys, } from "./base"; import { IRoomKeyRequestBody } from "../index"; import { ICrossSigningKey } from "../../client"; import { IOlmDevice } from "../algorithms/megolm"; import { IRoomEncryption } from "../RoomList"; import { InboundGroupSessionData } from "../OlmDevice"; -import { IEncryptedPayload } from "../aes"; /** * Internal module. in-memory storage for e2e. @@ -45,9 +44,9 @@ import { IEncryptedPayload } from "../aes"; */ export class MemoryCryptoStore implements CryptoStore { private outgoingRoomKeyRequests: OutgoingRoomKeyRequest[] = []; - private account: string = null; - private crossSigningKeys: Record = null; - private privateKeys: Record = {}; + private account: string | null = null; + private crossSigningKeys: Record | null = null; + private privateKeys: Partial = {}; private sessions: { [deviceKey: string]: { [sessionId: string]: ISessionInfo } } = {}; private sessionProblems: { [deviceKey: string]: IProblem[] } = {}; @@ -280,7 +279,7 @@ export class MemoryCryptoStore implements CryptoStore { // Olm Account - public getAccount(txn: unknown, func: (accountPickle: string) => void) { + public getAccount(txn: unknown, func: (accountPickle: string | null) => void) { func(this.account); } @@ -288,12 +287,16 @@ export class MemoryCryptoStore implements CryptoStore { this.account = accountPickle; } - public getCrossSigningKeys(txn: unknown, func: (keys: Record) => void): void { + public getCrossSigningKeys(txn: unknown, func: (keys: Record | null) => void): void { func(this.crossSigningKeys); } - public getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => void, type: string): void { - const result = this.privateKeys[type]; + public getSecretStorePrivateKey( + txn: unknown, + func: (key: SecretStorePrivateKeys[K] | null) => void, + type: K, + ): void { + const result = this.privateKeys[type] as SecretStorePrivateKeys[K] | undefined; func(result || null); } @@ -301,7 +304,11 @@ export class MemoryCryptoStore implements CryptoStore { this.crossSigningKeys = keys; } - public storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void { + public storeSecretStorePrivateKey( + txn: unknown, + type: K, + key: SecretStorePrivateKeys[K], + ): void { this.privateKeys[type] = key; } diff --git a/src/index.ts b/src/index.ts index 4b8422435..a93c2714d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,22 +15,12 @@ limitations under the License. */ import * as matrixcs from "./matrix"; -import * as utils from "./utils"; -import { logger } from './logger'; if (global.__js_sdk_entrypoint) { throw new Error("Multiple matrix-js-sdk entrypoints detected!"); } global.__js_sdk_entrypoint = true; -try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const crypto = require('crypto'); - utils.setCrypto(crypto); -} catch (err) { - logger.log('nodejs was compiled without crypto support'); -} - export * from "./matrix"; export default matrixcs; diff --git a/src/utils.ts b/src/utils.ts index 591803296..04a70a64a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -23,7 +23,6 @@ limitations under the License. import unhomoglyph from "unhomoglyph"; import promiseRetry from "p-retry"; -import type * as NodeCrypto from "crypto"; import { MatrixEvent } from "./models/event"; import { M_TIMESTAMP } from "./@types/location"; import { ReceiptType } from "./@types/read_receipts"; @@ -453,20 +452,6 @@ export function simpleRetryOperation(promiseFn: (attempt: number) => Promise< }); } -// We need to be able to access the Node.js crypto library from within the -// Matrix SDK without needing to `require("crypto")`, which will fail in -// browsers. So `index.ts` will call `setCrypto` to store it, and when we need -// it, we can call `getCrypto`. -let crypto: typeof NodeCrypto; - -export function setCrypto(c: typeof NodeCrypto) { - crypto = c; -} - -export function getCrypto(): typeof NodeCrypto { - return crypto; -} - // String averaging inspired by https://stackoverflow.com/a/2510816 // Dev note: We make the alphabet a string because it's easier to write syntactically // than arrays. Thankfully, strings implement the useful parts of the Array interface