1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-26 17:03:12 +03:00

Even moar typescriptification

This commit is contained in:
Michael Telatynski
2021-06-24 19:19:41 +01:00
parent b4dc1e1555
commit 40aa6ba96a
13 changed files with 389 additions and 328 deletions

View File

@@ -21,25 +21,23 @@ import { MemoryCryptoStore } from '../../../src/crypto/store/memory-crypto-store
import 'fake-indexeddb/auto'; import 'fake-indexeddb/auto';
import 'jest-localstorage-mock'; import 'jest-localstorage-mock';
import { import { RoomKeyRequestState } from '../../../src/crypto/OutgoingRoomKeyRequestManager';
ROOM_KEY_REQUEST_STATES,
} from '../../../src/crypto/OutgoingRoomKeyRequestManager';
const requests = [ const requests = [
{ {
requestId: "A", requestId: "A",
requestBody: { session_id: "A", room_id: "A" }, requestBody: { session_id: "A", room_id: "A" },
state: ROOM_KEY_REQUEST_STATES.SENT, state: RoomKeyRequestState.Sent,
}, },
{ {
requestId: "B", requestId: "B",
requestBody: { session_id: "B", room_id: "B" }, requestBody: { session_id: "B", room_id: "B" },
state: ROOM_KEY_REQUEST_STATES.SENT, state: RoomKeyRequestState.Sent,
}, },
{ {
requestId: "C", requestId: "C",
requestBody: { session_id: "C", room_id: "C" }, requestBody: { session_id: "C", room_id: "C" },
state: ROOM_KEY_REQUEST_STATES.UNSENT, state: RoomKeyRequestState.Unsent,
}, },
]; ];
@@ -68,9 +66,9 @@ describe.each([
it("getAllOutgoingRoomKeyRequestsByState retrieves all entries in a given state", it("getAllOutgoingRoomKeyRequestsByState retrieves all entries in a given state",
async () => { async () => {
const r = await const r = await
store.getAllOutgoingRoomKeyRequestsByState(ROOM_KEY_REQUEST_STATES.SENT); store.getAllOutgoingRoomKeyRequestsByState(RoomKeyRequestState.Sent);
expect(r).toHaveLength(2); expect(r).toHaveLength(2);
requests.filter((e) => e.state == ROOM_KEY_REQUEST_STATES.SENT).forEach((e) => { requests.filter((e) => e.state === RoomKeyRequestState.Sent).forEach((e) => {
expect(r).toContainEqual(e); expect(r).toContainEqual(e);
}); });
}); });
@@ -78,10 +76,10 @@ describe.each([
test("getOutgoingRoomKeyRequestByState retrieves any entry in a given state", test("getOutgoingRoomKeyRequestByState retrieves any entry in a given state",
async () => { async () => {
const r = const r =
await store.getOutgoingRoomKeyRequestByState([ROOM_KEY_REQUEST_STATES.SENT]); await store.getOutgoingRoomKeyRequestByState([RoomKeyRequestState.Sent]);
expect(r).not.toBeNull(); expect(r).not.toBeNull();
expect(r).not.toBeUndefined(); expect(r).not.toBeUndefined();
expect(r.state).toEqual(ROOM_KEY_REQUEST_STATES.SENT); expect(r.state).toEqual(RoomKeyRequestState.Sent);
expect(requests).toContainEqual(r); expect(requests).toContainEqual(r);
}); });
}); });

View File

@@ -59,7 +59,7 @@ import {
IKeyBackupPrepareOpts, IKeyBackupPrepareOpts,
IKeyBackupRestoreOpts, IKeyBackupRestoreOpts,
IKeyBackupRestoreResult, IKeyBackupRestoreResult,
IKeyBackupVersion, IKeyBackupInfo,
} from "./crypto/keybackup"; } from "./crypto/keybackup";
import { IIdentityServerProvider } from "./@types/IIdentityServerProvider"; import { IIdentityServerProvider } from "./@types/IIdentityServerProvider";
import type Request from "request"; import type Request from "request";
@@ -116,6 +116,7 @@ import { ReadStream } from "fs";
import { WebStorageSessionStore } from "./store/session/webstorage"; import { WebStorageSessionStore } from "./store/session/webstorage";
import { BackupManager, IKeyBackupCheck, IPreparedKeyBackupVersion, TrustInfo } from "./crypto/backup"; import { BackupManager, IKeyBackupCheck, IPreparedKeyBackupVersion, TrustInfo } from "./crypto/backup";
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from "./models/MSC3089TreeSpace"; import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from "./models/MSC3089TreeSpace";
import { ISignatures } from "./@types/signed";
export type Store = StubStore | MemoryStore | LocalIndexedDBStoreBackend | RemoteIndexedDBStoreBackend; export type Store = StubStore | MemoryStore | LocalIndexedDBStoreBackend | RemoteIndexedDBStoreBackend;
export type SessionStore = WebStorageSessionStore; export type SessionStore = WebStorageSessionStore;
@@ -375,6 +376,39 @@ interface ICapabilities {
"m.room_versions"?: IRoomVersionsCapability; "m.room_versions"?: IRoomVersionsCapability;
} }
/* eslint-disable camelcase */
export interface ICrossSigningKey {
keys: { [algorithm: string]: string };
signatures?: ISignatures;
usage: string[];
user_id: string;
}
enum CrossSigningKeyType {
MasterKey = "master_key",
SelfSigningKey = "self_signing_key",
UserSigningKey = "user_signing_key",
}
export type CrossSigningKeys = Record<CrossSigningKeyType, ICrossSigningKey>;
export interface ISignedKey {
keys: Record<string, string>;
signatures: ISignatures;
user_id: string;
algorithms: string[];
device_id: string;
}
/* eslint-enable camelcase */
export type KeySignatures = Record<string, Record<string, ICrossSigningKey | ISignedKey>>;
interface IUploadKeySignaturesResponse {
failures: Record<string, Record<string, {
errcode: string;
error: string;
}>>;
}
/** /**
* Represents a Matrix Client. Only directly construct this if you want to use * Represents a Matrix Client. Only directly construct this if you want to use
* custom modules. Normally, {@link createClient} should be used * custom modules. Normally, {@link createClient} should be used
@@ -2086,7 +2120,7 @@ export class MatrixClient extends EventEmitter {
* Get information about the current key backup. * Get information about the current key backup.
* @returns {Promise} Information object from API or null * @returns {Promise} Information object from API or null
*/ */
public getKeyBackupVersion(): Promise<IKeyBackupVersion> { public getKeyBackupVersion(): Promise<IKeyBackupInfo> {
return this.http.authedRequest( return this.http.authedRequest(
undefined, "GET", "/room_keys/version", undefined, undefined, undefined, "GET", "/room_keys/version", undefined, undefined,
{ prefix: PREFIX_UNSTABLE }, { prefix: PREFIX_UNSTABLE },
@@ -2120,7 +2154,7 @@ export class MatrixClient extends EventEmitter {
* ] * ]
* } * }
*/ */
public isKeyBackupTrusted(info: IKeyBackupVersion): Promise<TrustInfo> { public isKeyBackupTrusted(info: IKeyBackupInfo): Promise<TrustInfo> {
return this.crypto.backupManager.isKeyBackupTrusted(info); return this.crypto.backupManager.isKeyBackupTrusted(info);
} }
@@ -2143,7 +2177,7 @@ export class MatrixClient extends EventEmitter {
* @param {object} info Backup information object as returned by getKeyBackupVersion * @param {object} info Backup information object as returned by getKeyBackupVersion
* @returns {Promise<void>} Resolves when complete. * @returns {Promise<void>} Resolves when complete.
*/ */
public enableKeyBackup(info: IKeyBackupVersion): Promise<void> { public enableKeyBackup(info: IKeyBackupInfo): Promise<void> {
if (!this.crypto) { if (!this.crypto) {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }
@@ -2219,7 +2253,7 @@ export class MatrixClient extends EventEmitter {
* @returns {Promise<object>} Object with 'version' param indicating the version created * @returns {Promise<object>} Object with 'version' param indicating the version created
*/ */
// TODO: Fix types // TODO: Fix types
public async createKeyBackupVersion(info: IKeyBackupVersion): Promise<IKeyBackupVersion> { public async createKeyBackupVersion(info: IKeyBackupInfo): Promise<IKeyBackupInfo> {
if (!this.crypto) { if (!this.crypto) {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }
@@ -2375,7 +2409,7 @@ export class MatrixClient extends EventEmitter {
* @param {object} backupInfo Backup metadata from `checkKeyBackup` * @param {object} backupInfo Backup metadata from `checkKeyBackup`
* @return {Promise<Uint8Array>} key backup key * @return {Promise<Uint8Array>} key backup key
*/ */
public keyBackupKeyFromPassword(password: string, backupInfo: IKeyBackupVersion): Promise<Uint8Array> { public keyBackupKeyFromPassword(password: string, backupInfo: IKeyBackupInfo): Promise<Uint8Array> {
return keyFromAuthData(backupInfo.auth_data, password); return keyFromAuthData(backupInfo.auth_data, password);
} }
@@ -2388,7 +2422,7 @@ export class MatrixClient extends EventEmitter {
* @param {string} recoveryKey The recovery key * @param {string} recoveryKey The recovery key
* @return {Uint8Array} key backup key * @return {Uint8Array} key backup key
*/ */
public keyBackupKeyFromRecoveryKey(recoveryKey: string): ArrayLike<number> { public keyBackupKeyFromRecoveryKey(recoveryKey: string): Uint8Array {
return decodeRecoveryKey(recoveryKey); return decodeRecoveryKey(recoveryKey);
} }
@@ -2410,7 +2444,7 @@ export class MatrixClient extends EventEmitter {
password: string, password: string,
targetRoomId: string, targetRoomId: string,
targetSessionId: string, targetSessionId: string,
backupInfo: IKeyBackupVersion, backupInfo: IKeyBackupInfo,
opts: IKeyBackupRestoreOpts, opts: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult> { ): Promise<IKeyBackupRestoreResult> {
const privKey = await keyFromAuthData(backupInfo.auth_data, password); const privKey = await keyFromAuthData(backupInfo.auth_data, password);
@@ -2434,7 +2468,7 @@ export class MatrixClient extends EventEmitter {
*/ */
// TODO: Types // TODO: Types
public async restoreKeyBackupWithSecretStorage( public async restoreKeyBackupWithSecretStorage(
backupInfo: IKeyBackupVersion, backupInfo: IKeyBackupInfo,
targetRoomId?: string, targetRoomId?: string,
targetSessionId?: string, targetSessionId?: string,
opts?: IKeyBackupRestoreOpts, opts?: IKeyBackupRestoreOpts,
@@ -2474,7 +2508,7 @@ export class MatrixClient extends EventEmitter {
recoveryKey: string, recoveryKey: string,
targetRoomId: string, targetRoomId: string,
targetSessionId: string, targetSessionId: string,
backupInfo: IKeyBackupVersion, backupInfo: IKeyBackupInfo,
opts: IKeyBackupRestoreOpts, opts: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult> { ): Promise<IKeyBackupRestoreResult> {
const privKey = decodeRecoveryKey(recoveryKey); const privKey = decodeRecoveryKey(recoveryKey);
@@ -2485,7 +2519,7 @@ export class MatrixClient extends EventEmitter {
public async restoreKeyBackupWithCache( public async restoreKeyBackupWithCache(
targetRoomId: string, targetRoomId: string,
targetSessionId: string, targetSessionId: string,
backupInfo: IKeyBackupVersion, backupInfo: IKeyBackupInfo,
opts?: IKeyBackupRestoreOpts, opts?: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult> { ): Promise<IKeyBackupRestoreResult> {
const privKey = await this.crypto.getSessionBackupPrivateKey(); const privKey = await this.crypto.getSessionBackupPrivateKey();
@@ -2499,7 +2533,7 @@ export class MatrixClient extends EventEmitter {
privKey: ArrayLike<number>, privKey: ArrayLike<number>,
targetRoomId: string, targetRoomId: string,
targetSessionId: string, targetSessionId: string,
backupInfo: IKeyBackupVersion, backupInfo: IKeyBackupInfo,
opts?: IKeyBackupRestoreOpts, opts?: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult> { ): Promise<IKeyBackupRestoreResult> {
const cacheCompleteCallback = opts?.cacheCompleteCallback; const cacheCompleteCallback = opts?.cacheCompleteCallback;
@@ -7090,7 +7124,7 @@ export class MatrixClient extends EventEmitter {
return this.http.authedRequest(callback, "POST", "/keys/upload", undefined, content); return this.http.authedRequest(callback, "POST", "/keys/upload", undefined, content);
} }
public uploadKeySignatures(content: any): Promise<any> { // TODO: Types public uploadKeySignatures(content: KeySignatures): Promise<IUploadKeySignaturesResponse> {
return this.http.authedRequest( return this.http.authedRequest(
undefined, "POST", '/keys/signatures/upload', undefined, undefined, "POST", '/keys/signatures/upload', undefined,
content, { content, {
@@ -7189,7 +7223,7 @@ export class MatrixClient extends EventEmitter {
return this.http.authedRequest(undefined, "GET", path, qps, undefined); return this.http.authedRequest(undefined, "GET", path, qps, undefined);
} }
public uploadDeviceSigningKeys(auth: any, keys: any): Promise<any> { // TODO: Lots of types public uploadDeviceSigningKeys(auth: any, keys: CrossSigningKeys): Promise<{}> { // TODO: types
const data = Object.assign({}, keys); const data = Object.assign({}, keys);
if (auth) Object.assign(data, { auth }); if (auth) Object.assign(data, { auth });
return this.http.authedRequest( return this.http.authedRequest(
@@ -7601,7 +7635,11 @@ export class MatrixClient extends EventEmitter {
* supplied. * supplied.
* @return {Promise} Resolves to the result object * @return {Promise} Resolves to the result object
*/ */
public sendToDevice(eventType: string, contentMap: any, txnId?: string): Promise<any> { // TODO: Types public sendToDevice(
eventType: string,
contentMap: { [userId: string]: { [deviceId: string]: Record<string, any>; } },
txnId?: string,
): Promise<{}> {
const path = utils.encodeUri("/sendToDevice/$eventType/$txnId", { const path = utils.encodeUri("/sendToDevice/$eventType/$txnId", {
$eventType: eventType, $eventType: eventType,
$txnId: txnId ? txnId : this.makeTxnId(), $txnId: txnId ? txnId : this.makeTxnId(),

View File

@@ -28,26 +28,27 @@ import { decryptAES, encryptAES } from './aes';
import { PkSigning } from "@matrix-org/olm"; import { PkSigning } from "@matrix-org/olm";
import { DeviceInfo } from "./deviceinfo"; import { DeviceInfo } from "./deviceinfo";
import { SecretStorage } from "./SecretStorage"; import { SecretStorage } from "./SecretStorage";
import { CryptoStore, MatrixClient } from "../client"; import { CryptoStore, ICrossSigningKey, ISignedKey, MatrixClient } from "../client";
import { OlmDevice } from "./OlmDevice"; import { OlmDevice } from "./OlmDevice";
import { ICryptoCallbacks } from "../matrix"; import { ICryptoCallbacks } from "../matrix";
import { ISignatures } from "../@types/signed";
const KEY_REQUEST_TIMEOUT_MS = 1000 * 60; const KEY_REQUEST_TIMEOUT_MS = 1000 * 60;
function publicKeyFromKeyInfo(keyInfo: any): any { // TODO types function publicKeyFromKeyInfo(keyInfo: ICrossSigningKey): string {
// `keys` is an object with { [`ed25519:${pubKey}`]: pubKey } // `keys` is an object with { [`ed25519:${pubKey}`]: pubKey }
// We assume only a single key, and we want the bare form without type // We assume only a single key, and we want the bare form without type
// prefix, so we select the values. // prefix, so we select the values.
return Object.values(keyInfo.keys)[0]; return Object.values(keyInfo.keys)[0];
} }
interface ICacheCallbacks { export interface ICacheCallbacks {
getCrossSigningKeyCache?(type: string, expectedPublicKey?: string): Promise<Uint8Array>; getCrossSigningKeyCache?(type: string, expectedPublicKey?: string): Promise<Uint8Array>;
storeCrossSigningKeyCache?(type: string, key: Uint8Array): Promise<void>; storeCrossSigningKeyCache?(type: string, key: Uint8Array): Promise<void>;
} }
export class CrossSigningInfo extends EventEmitter { export class CrossSigningInfo extends EventEmitter {
public keys: Record<string, any> = {}; // TODO types public keys: Record<string, ICrossSigningKey> = {};
public firstUse = true; public firstUse = true;
// This tracks whether we've ever verified this user with any identity. // This tracks whether we've ever verified this user with any identity.
// When you verify a user, any devices online at the time that receive // When you verify a user, any devices online at the time that receive
@@ -368,8 +369,8 @@ export class CrossSigningInfo extends EventEmitter {
this.keys = {}; this.keys = {};
} }
public setKeys(keys: Record<string, any>): void { public setKeys(keys: Record<string, ICrossSigningKey>): void {
const signingKeys: Record<string, object> = {}; const signingKeys: Record<string, ICrossSigningKey> = {};
if (keys.master) { if (keys.master) {
if (keys.master.user_id !== this.userId) { if (keys.master.user_id !== this.userId) {
const error = "Mismatched user ID " + keys.master.user_id + const error = "Mismatched user ID " + keys.master.user_id +
@@ -448,7 +449,7 @@ export class CrossSigningInfo extends EventEmitter {
} }
} }
public async signObject<T extends object>(data: T, type: string): Promise<T> { public async signObject<T extends object>(data: T, type: string): Promise<T & { signatures: ISignatures }> {
if (!this.keys[type]) { if (!this.keys[type]) {
throw new Error( throw new Error(
"Attempted to sign with " + type + " key but no such key present", "Attempted to sign with " + type + " key but no such key present",
@@ -457,13 +458,13 @@ export class CrossSigningInfo extends EventEmitter {
const [pubkey, signing] = await this.getCrossSigningKey(type); const [pubkey, signing] = await this.getCrossSigningKey(type);
try { try {
pkSign(data, signing, this.userId, pubkey); pkSign(data, signing, this.userId, pubkey);
return data; return data as T & { signatures: ISignatures };
} finally { } finally {
signing.free(); signing.free();
} }
} }
public async signUser(key: CrossSigningInfo): Promise<object> { public async signUser(key: CrossSigningInfo): Promise<ICrossSigningKey> {
if (!this.keys.user_signing) { if (!this.keys.user_signing) {
logger.info("No user signing key: not signing user"); logger.info("No user signing key: not signing user");
return; return;
@@ -471,7 +472,7 @@ export class CrossSigningInfo extends EventEmitter {
return this.signObject(key.keys.master, "user_signing"); return this.signObject(key.keys.master, "user_signing");
} }
public async signDevice(userId: string, device: DeviceInfo): Promise<object> { public async signDevice(userId: string, device: DeviceInfo): Promise<ISignedKey> {
if (userId !== this.userId) { if (userId !== this.userId) {
throw new Error( throw new Error(
`Trying to sign ${userId}'s device; can only sign our own device`, `Trying to sign ${userId}'s device; can only sign our own device`,
@@ -481,7 +482,7 @@ export class CrossSigningInfo extends EventEmitter {
logger.info("No self signing key: not signing device"); logger.info("No self signing key: not signing device");
return; return;
} }
return this.signObject( return this.signObject<Omit<ISignedKey, "signatures">>(
{ {
algorithms: device.algorithms, algorithms: device.algorithms,
keys: device.keys, keys: device.keys,

View File

@@ -1,11 +1,24 @@
import { logger } from "../logger"; import { logger } from "../logger";
import { MatrixEvent } from "../models/event"; import { MatrixEvent } from "../models/event";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { createCryptoStoreCacheCallbacks } from "./CrossSigning"; import { createCryptoStoreCacheCallbacks, ICacheCallbacks } from "./CrossSigning";
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import { PREFIX_UNSTABLE } from "../http-api";
import { Crypto } from "./index";
import { import {
PREFIX_UNSTABLE, CrossSigningKeys,
} from "../http-api"; ICrossSigningKey,
ICryptoCallbacks,
ISecretStorageKeyInfo,
ISignedKey,
KeySignatures,
} from "../matrix";
import { IKeyBackupInfo } from "./keybackup";
interface ICrossSigningKeys {
authUpload(authData: any): Promise<{}>;
keys: Record<string, ICrossSigningKey>;
}
/** /**
* Builds an EncryptionSetupOperation by calling any of the add.. methods. * Builds an EncryptionSetupOperation by calling any of the add.. methods.
@@ -17,18 +30,23 @@ import {
* more than once. * more than once.
*/ */
export class EncryptionSetupBuilder { export class EncryptionSetupBuilder {
public readonly accountDataClientAdapter: AccountDataClientAdapter;
public readonly crossSigningCallbacks: CrossSigningCallbacks;
public readonly ssssCryptoCallbacks: SSSSCryptoCallbacks;
private crossSigningKeys: ICrossSigningKeys = null;
private keySignatures: KeySignatures = null;
private keyBackupInfo: IKeyBackupInfo = null;
private sessionBackupPrivateKey: Uint8Array;
/** /**
* @param {Object.<String, MatrixEvent>} accountData pre-existing account data, will only be read, not written. * @param {Object.<String, MatrixEvent>} accountData pre-existing account data, will only be read, not written.
* @param {CryptoCallbacks} delegateCryptoCallbacks crypto callbacks to delegate to if the key isn't in cache yet * @param {CryptoCallbacks} delegateCryptoCallbacks crypto callbacks to delegate to if the key isn't in cache yet
*/ */
constructor(accountData, delegateCryptoCallbacks) { constructor(accountData: Record<string, MatrixEvent>, delegateCryptoCallbacks: ICryptoCallbacks) {
this.accountDataClientAdapter = new AccountDataClientAdapter(accountData); this.accountDataClientAdapter = new AccountDataClientAdapter(accountData);
this.crossSigningCallbacks = new CrossSigningCallbacks(); this.crossSigningCallbacks = new CrossSigningCallbacks();
this.ssssCryptoCallbacks = new SSSSCryptoCallbacks(delegateCryptoCallbacks); this.ssssCryptoCallbacks = new SSSSCryptoCallbacks(delegateCryptoCallbacks);
this._crossSigningKeys = null;
this._keySignatures = null;
this._keyBackupInfo = null;
} }
/** /**
@@ -42,8 +60,8 @@ export class EncryptionSetupBuilder {
* an empty authDict, to obtain the flows. * an empty authDict, to obtain the flows.
* @param {Object} keys the new keys * @param {Object} keys the new keys
*/ */
addCrossSigningKeys(authUpload, keys) { public addCrossSigningKeys(authUpload: ICrossSigningKeys["authUpload"], keys: ICrossSigningKeys["keys"]): void {
this._crossSigningKeys = { authUpload, keys }; this.crossSigningKeys = { authUpload, keys };
} }
/** /**
@@ -54,8 +72,8 @@ export class EncryptionSetupBuilder {
* *
* @param {Object} keyBackupInfo as received from/sent to the server * @param {Object} keyBackupInfo as received from/sent to the server
*/ */
addSessionBackup(keyBackupInfo) { public addSessionBackup(keyBackupInfo: IKeyBackupInfo): void {
this._keyBackupInfo = keyBackupInfo; this.keyBackupInfo = keyBackupInfo;
} }
/** /**
@@ -65,8 +83,8 @@ export class EncryptionSetupBuilder {
* *
* @param {Uint8Array} privateKey * @param {Uint8Array} privateKey
*/ */
addSessionBackupPrivateKeyToCache(privateKey) { public addSessionBackupPrivateKeyToCache(privateKey: Uint8Array): void {
this._sessionBackupPrivateKey = privateKey; this.sessionBackupPrivateKey = privateKey;
} }
/** /**
@@ -75,14 +93,14 @@ export class EncryptionSetupBuilder {
* *
* @param {String} userId * @param {String} userId
* @param {String} deviceId * @param {String} deviceId
* @param {String} signature * @param {Object} signature
*/ */
addKeySignature(userId, deviceId, signature) { public addKeySignature(userId: string, deviceId: string, signature: ISignedKey): void {
if (!this._keySignatures) { if (!this.keySignatures) {
this._keySignatures = {}; this.keySignatures = {};
} }
const userSignatures = this._keySignatures[userId] || {}; const userSignatures = this.keySignatures[userId] || {};
this._keySignatures[userId] = userSignatures; this.keySignatures[userId] = userSignatures;
userSignatures[deviceId] = signature; userSignatures[deviceId] = signature;
} }
@@ -91,7 +109,7 @@ export class EncryptionSetupBuilder {
* @param {Object} content * @param {Object} content
* @return {Promise} * @return {Promise}
*/ */
setAccountData(type, content) { public setAccountData(type: string, content: object): Promise<void> {
return this.accountDataClientAdapter.setAccountData(type, content); return this.accountDataClientAdapter.setAccountData(type, content);
} }
@@ -99,13 +117,13 @@ export class EncryptionSetupBuilder {
* builds the operation containing all the parts that have been added to the builder * builds the operation containing all the parts that have been added to the builder
* @return {EncryptionSetupOperation} * @return {EncryptionSetupOperation}
*/ */
buildOperation() { public buildOperation(): EncryptionSetupOperation {
const accountData = this.accountDataClientAdapter._values; const accountData = this.accountDataClientAdapter.values;
return new EncryptionSetupOperation( return new EncryptionSetupOperation(
accountData, accountData,
this._crossSigningKeys, this.crossSigningKeys,
this._keyBackupInfo, this.keyBackupInfo,
this._keySignatures, this.keySignatures,
); );
} }
@@ -118,9 +136,9 @@ export class EncryptionSetupBuilder {
* @param {Crypto} crypto * @param {Crypto} crypto
* @return {Promise} * @return {Promise}
*/ */
async persist(crypto) { public async persist(crypto: Crypto): Promise<void> {
// store private keys in cache // store private keys in cache
if (this._crossSigningKeys) { if (this.crossSigningKeys) {
const cacheCallbacks = createCryptoStoreCacheCallbacks(crypto.cryptoStore, crypto.olmDevice); const cacheCallbacks = createCryptoStoreCacheCallbacks(crypto.cryptoStore, crypto.olmDevice);
for (const type of ["master", "self_signing", "user_signing"]) { for (const type of ["master", "self_signing", "user_signing"]) {
logger.log(`Cache ${type} cross-signing private key locally`); logger.log(`Cache ${type} cross-signing private key locally`);
@@ -132,13 +150,13 @@ export class EncryptionSetupBuilder {
'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], 'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => { (txn) => {
crypto.cryptoStore.storeCrossSigningKeys( crypto.cryptoStore.storeCrossSigningKeys(
txn, this._crossSigningKeys.keys); txn, this.crossSigningKeys.keys);
}, },
); );
} }
// store session backup key in cache // store session backup key in cache
if (this._sessionBackupPrivateKey) { if (this.sessionBackupPrivateKey) {
await crypto.storeSessionBackupPrivateKey(this._sessionBackupPrivateKey); await crypto.storeSessionBackupPrivateKey(this.sessionBackupPrivateKey);
} }
} }
} }
@@ -156,58 +174,58 @@ export class EncryptionSetupOperation {
* @param {Object} keyBackupInfo * @param {Object} keyBackupInfo
* @param {Object} keySignatures * @param {Object} keySignatures
*/ */
constructor(accountData, crossSigningKeys, keyBackupInfo, keySignatures) { constructor(
this._accountData = accountData; private readonly accountData: Map<string, object>,
this._crossSigningKeys = crossSigningKeys; private readonly crossSigningKeys: ICrossSigningKeys,
this._keyBackupInfo = keyBackupInfo; private readonly keyBackupInfo: IKeyBackupInfo,
this._keySignatures = keySignatures; private readonly keySignatures: KeySignatures,
} ) {}
/** /**
* Runs the (remaining part of, in the future) operation by sending requests to the server. * Runs the (remaining part of, in the future) operation by sending requests to the server.
* @param {Crypto} crypto * @param {Crypto} crypto
*/ */
async apply(crypto) { public async apply(crypto: Crypto): Promise<void> {
const baseApis = crypto.baseApis; const baseApis = crypto.baseApis;
// upload cross-signing keys // upload cross-signing keys
if (this._crossSigningKeys) { if (this.crossSigningKeys) {
const keys = {}; const keys: Partial<CrossSigningKeys> = {};
for (const [name, key] of Object.entries(this._crossSigningKeys.keys)) { for (const [name, key] of Object.entries(this.crossSigningKeys.keys)) {
keys[name + "_key"] = key; keys[name + "_key"] = key;
} }
// We must only call `uploadDeviceSigningKeys` from inside this auth // We must only call `uploadDeviceSigningKeys` from inside this auth
// helper to ensure we properly handle auth errors. // helper to ensure we properly handle auth errors.
await this._crossSigningKeys.authUpload(authDict => { await this.crossSigningKeys.authUpload(authDict => {
return baseApis.uploadDeviceSigningKeys(authDict, keys); return baseApis.uploadDeviceSigningKeys(authDict, keys as CrossSigningKeys);
}); });
// pass the new keys to the main instance of our own CrossSigningInfo. // pass the new keys to the main instance of our own CrossSigningInfo.
crypto.crossSigningInfo.setKeys(this._crossSigningKeys.keys); crypto.crossSigningInfo.setKeys(this.crossSigningKeys.keys);
} }
// set account data // set account data
if (this._accountData) { if (this.accountData) {
for (const [type, content] of this._accountData) { for (const [type, content] of this.accountData) {
await baseApis.setAccountData(type, content); await baseApis.setAccountData(type, content);
} }
} }
// upload first cross-signing signatures with the new key // upload first cross-signing signatures with the new key
// (e.g. signing our own device) // (e.g. signing our own device)
if (this._keySignatures) { if (this.keySignatures) {
await baseApis.uploadKeySignatures(this._keySignatures); await baseApis.uploadKeySignatures(this.keySignatures);
} }
// need to create/update key backup info // need to create/update key backup info
if (this._keyBackupInfo) { if (this.keyBackupInfo) {
if (this._keyBackupInfo.version) { if (this.keyBackupInfo.version) {
// session backup signature // session backup signature
// The backup is trusted because the user provided the private key. // The backup is trusted because the user provided the private key.
// Sign the backup with the cross signing key so the key backup can // Sign the backup with the cross signing key so the key backup can
// be trusted via cross-signing. // be trusted via cross-signing.
await baseApis.http.authedRequest( await baseApis.http.authedRequest(
undefined, "PUT", "/room_keys/version/" + this._keyBackupInfo.version, undefined, "PUT", "/room_keys/version/" + this.keyBackupInfo.version,
undefined, { undefined, {
algorithm: this._keyBackupInfo.algorithm, algorithm: this.keyBackupInfo.algorithm,
auth_data: this._keyBackupInfo.auth_data, auth_data: this.keyBackupInfo.auth_data,
}, },
{ prefix: PREFIX_UNSTABLE }, { prefix: PREFIX_UNSTABLE },
); );
@@ -215,7 +233,7 @@ export class EncryptionSetupOperation {
// add new key backup // add new key backup
await baseApis.http.authedRequest( await baseApis.http.authedRequest(
undefined, "POST", "/room_keys/version", undefined, "POST", "/room_keys/version",
undefined, this._keyBackupInfo, undefined, this.keyBackupInfo,
{ prefix: PREFIX_UNSTABLE }, { prefix: PREFIX_UNSTABLE },
); );
} }
@@ -228,20 +246,20 @@ export class EncryptionSetupOperation {
* implementing the methods related to account data in MatrixClient * implementing the methods related to account data in MatrixClient
*/ */
class AccountDataClientAdapter extends EventEmitter { class AccountDataClientAdapter extends EventEmitter {
public readonly values = new Map<string, object>();
/** /**
* @param {Object.<String, MatrixEvent>} accountData existing account data * @param {Object.<String, MatrixEvent>} existingValues existing account data
*/ */
constructor(accountData) { constructor(private readonly existingValues: Record<string, MatrixEvent>) {
super(); super();
this._existingValues = accountData;
this._values = new Map();
} }
/** /**
* @param {String} type * @param {String} type
* @return {Promise<Object>} the content of the account data * @return {Promise<Object>} the content of the account data
*/ */
getAccountDataFromServer(type) { public getAccountDataFromServer(type: string): Promise<object> {
return Promise.resolve(this.getAccountData(type)); return Promise.resolve(this.getAccountData(type));
} }
@@ -249,12 +267,12 @@ class AccountDataClientAdapter extends EventEmitter {
* @param {String} type * @param {String} type
* @return {Object} the content of the account data * @return {Object} the content of the account data
*/ */
getAccountData(type) { public getAccountData(type: string): object {
const modifiedValue = this._values.get(type); const modifiedValue = this.values.get(type);
if (modifiedValue) { if (modifiedValue) {
return modifiedValue; return modifiedValue;
} }
const existingValue = this._existingValues[type]; const existingValue = this.existingValues[type];
if (existingValue) { if (existingValue) {
return existingValue.getContent(); return existingValue.getContent();
} }
@@ -266,9 +284,9 @@ class AccountDataClientAdapter extends EventEmitter {
* @param {Object} content * @param {Object} content
* @return {Promise} * @return {Promise}
*/ */
setAccountData(type, content) { public setAccountData(type: string, content: object): Promise<void> {
const lastEvent = this._values.get(type); const lastEvent = this.values.get(type);
this._values.set(type, content); this.values.set(type, content);
// ensure accountData is emitted on the next tick, // ensure accountData is emitted on the next tick,
// as SecretStorage listens for it while calling this method // as SecretStorage listens for it while calling this method
// and it seems to rely on this. // and it seems to rely on this.
@@ -284,27 +302,25 @@ class AccountDataClientAdapter extends EventEmitter {
* by both cache callbacks (see createCryptoStoreCacheCallbacks) as non-cache callbacks. * by both cache callbacks (see createCryptoStoreCacheCallbacks) as non-cache callbacks.
* See CrossSigningInfo constructor * See CrossSigningInfo constructor
*/ */
class CrossSigningCallbacks { class CrossSigningCallbacks implements ICryptoCallbacks, ICacheCallbacks {
constructor() { public readonly privateKeys = new Map<string, Uint8Array>();
this.privateKeys = new Map();
}
// cache callbacks // cache callbacks
getCrossSigningKeyCache(type, expectedPublicKey) { public getCrossSigningKeyCache(type: string, expectedPublicKey: string): Promise<Uint8Array> {
return this.getCrossSigningKey(type, expectedPublicKey); return this.getCrossSigningKey(type, expectedPublicKey);
} }
storeCrossSigningKeyCache(type, key) { public storeCrossSigningKeyCache(type: string, key: Uint8Array): Promise<void> {
this.privateKeys.set(type, key); this.privateKeys.set(type, key);
return Promise.resolve(); return Promise.resolve();
} }
// non-cache callbacks // non-cache callbacks
getCrossSigningKey(type, _expectedPubkey) { public getCrossSigningKey(type: string, expectedPubkey: string): Promise<Uint8Array> {
return Promise.resolve(this.privateKeys.get(type)); return Promise.resolve(this.privateKeys.get(type));
} }
saveCrossSigningKeys(privateKeys) { public saveCrossSigningKeys(privateKeys: Record<string, Uint8Array>) {
for (const [type, privateKey] of Object.entries(privateKeys)) { for (const [type, privateKey] of Object.entries(privateKeys)) {
this.privateKeys.set(type, privateKey); this.privateKeys.set(type, privateKey);
} }
@@ -316,39 +332,36 @@ class CrossSigningCallbacks {
* the SecretStorage crypto callbacks * the SecretStorage crypto callbacks
*/ */
class SSSSCryptoCallbacks { class SSSSCryptoCallbacks {
constructor(delegateCryptoCallbacks) { private readonly privateKeys = new Map<string, Uint8Array>();
this._privateKeys = new Map();
this._delegateCryptoCallbacks = delegateCryptoCallbacks;
}
async getSecretStorageKey({ keys }, name) { constructor(private readonly delegateCryptoCallbacks: ICryptoCallbacks) {}
public async getSecretStorageKey(
{ keys }: { keys: Record<string, object> },
name: string,
): Promise<[string, Uint8Array]> {
for (const keyId of Object.keys(keys)) { for (const keyId of Object.keys(keys)) {
const privateKey = this._privateKeys.get(keyId); const privateKey = this.privateKeys.get(keyId);
if (privateKey) { if (privateKey) {
return [keyId, privateKey]; return [keyId, privateKey];
} }
} }
// if we don't have the key cached yet, ask // if we don't have the key cached yet, ask
// for it to the general crypto callbacks and cache it // for it to the general crypto callbacks and cache it
if (this._delegateCryptoCallbacks) { if (this.delegateCryptoCallbacks) {
const result = await this._delegateCryptoCallbacks. const result = await this.delegateCryptoCallbacks.
getSecretStorageKey({ keys }, name); getSecretStorageKey({ keys }, name);
if (result) { if (result) {
const [keyId, privateKey] = result; const [keyId, privateKey] = result;
this._privateKeys.set(keyId, privateKey); this.privateKeys.set(keyId, privateKey);
} }
return result; return result;
} }
} }
addPrivateKey(keyId, keyInfo, privKey) { public addPrivateKey(keyId: string, keyInfo: ISecretStorageKeyInfo, privKey: Uint8Array): void {
this._privateKeys.set(keyId, privKey); this.privateKeys.set(keyId, privKey);
// Also pass along to application to cache if it wishes // Also pass along to application to cache if it wishes
if ( this.delegateCryptoCallbacks?.cacheSecretStorageKey?.(keyId, keyInfo, privKey);
this._delegateCryptoCallbacks &&
this._delegateCryptoCallbacks.cacheSecretStorageKey
) {
this._delegateCryptoCallbacks.cacheSecretStorageKey(keyId, keyInfo, privKey);
}
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
Copyright 2017 Vector Creations Ltd Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -15,6 +15,10 @@ limitations under the License.
*/ */
import { logger } from '../logger'; import { logger } from '../logger';
import {CryptoStore, MatrixClient} from "../client";
import {IRoomKeyRequestBody, IRoomKeyRequestRecipient} from "./index";
import { OutgoingRoomKeyRequest } from './store/base';
import {EventType} from "../@types/event";
/** /**
* Internal module. Management of outgoing room key requests. * Internal module. Management of outgoing room key requests.
@@ -57,61 +61,58 @@ const SEND_KEY_REQUESTS_DELAY_MS = 500;
* *
* @enum {number} * @enum {number}
*/ */
export const ROOM_KEY_REQUEST_STATES = { export enum RoomKeyRequestState {
/** request not yet sent */ /** request not yet sent */
UNSENT: 0, Unsent,
/** request sent, awaiting reply */ /** request sent, awaiting reply */
SENT: 1, Sent,
/** reply received, cancellation not yet sent */ /** reply received, cancellation not yet sent */
CANCELLATION_PENDING: 2, CancellationPending,
/** /**
* Cancellation not yet sent and will transition to UNSENT instead of * Cancellation not yet sent and will transition to UNSENT instead of
* being deleted once the cancellation has been sent. * being deleted once the cancellation has been sent.
*/ */
CANCELLATION_PENDING_AND_WILL_RESEND: 3, CancellationPendingAndWillResend,
}; }
export class OutgoingRoomKeyRequestManager { export class OutgoingRoomKeyRequestManager {
constructor(baseApis, deviceId, cryptoStore) { // handle for the delayed call to sendOutgoingRoomKeyRequests. Non-null
this._baseApis = baseApis; // if the callback has been set, or if it is still running.
this._deviceId = deviceId; private sendOutgoingRoomKeyRequestsTimer: NodeJS.Timeout = null;
this._cryptoStore = cryptoStore;
// handle for the delayed call to _sendOutgoingRoomKeyRequests. Non-null // sanity check to ensure that we don't end up with two concurrent runs
// if the callback has been set, or if it is still running. // of sendOutgoingRoomKeyRequests
this._sendOutgoingRoomKeyRequestsTimer = null; private sendOutgoingRoomKeyRequestsRunning = false;
// sanity check to ensure that we don't end up with two concurrent runs private clientRunning = false;
// of _sendOutgoingRoomKeyRequests
this._sendOutgoingRoomKeyRequestsRunning = false;
this._clientRunning = false; constructor(
} private readonly baseApis: MatrixClient,
private readonly deviceId: string,
private readonly cryptoStore: CryptoStore,
) {}
/** /**
* Called when the client is started. Sets background processes running. * Called when the client is started. Sets background processes running.
*/ */
start() { public start(): void {
this._clientRunning = true; this.clientRunning = true;
} }
/** /**
* Called when the client is stopped. Stops any running background processes. * Called when the client is stopped. Stops any running background processes.
*/ */
stop() { public stop(): void {
logger.log('stopping OutgoingRoomKeyRequestManager'); logger.log('stopping OutgoingRoomKeyRequestManager');
// stop the timer on the next run // stop the timer on the next run
this._clientRunning = false; this.clientRunning = false;
} }
/** /**
* Send any requests that have been queued * Send any requests that have been queued
*/ */
sendQueuedRequests() { public sendQueuedRequests(): void {
this._startTimer(); this.startTimer();
} }
/** /**
@@ -131,95 +132,99 @@ export class OutgoingRoomKeyRequestManager {
* pending list (or we have established that a similar request already * pending list (or we have established that a similar request already
* exists) * exists)
*/ */
async queueRoomKeyRequest(requestBody, recipients, resend=false) { public async queueRoomKeyRequest(
const req = await this._cryptoStore.getOutgoingRoomKeyRequest( requestBody: IRoomKeyRequestBody,
recipients: IRoomKeyRequestRecipient[],
resend = false,
): Promise<void> {
const req = await this.cryptoStore.getOutgoingRoomKeyRequest(
requestBody, requestBody,
); );
if (!req) { if (!req) {
await this._cryptoStore.getOrAddOutgoingRoomKeyRequest({ await this.cryptoStore.getOrAddOutgoingRoomKeyRequest({
requestBody: requestBody, requestBody: requestBody,
recipients: recipients, recipients: recipients,
requestId: this._baseApis.makeTxnId(), requestId: this.baseApis.makeTxnId(),
state: ROOM_KEY_REQUEST_STATES.UNSENT, state: RoomKeyRequestState.Unsent,
}); });
} else { } else {
switch (req.state) { switch (req.state) {
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND: case RoomKeyRequestState.CancellationPendingAndWillResend:
case ROOM_KEY_REQUEST_STATES.UNSENT: case RoomKeyRequestState.Unsent:
// nothing to do here, since we're going to send a request anyways // nothing to do here, since we're going to send a request anyways
return; return;
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING: { case RoomKeyRequestState.CancellationPending: {
// existing request is about to be cancelled. If we want to // existing request is about to be cancelled. If we want to
// resend, then change the state so that it resends after // resend, then change the state so that it resends after
// cancelling. Otherwise, just cancel the cancellation. // cancelling. Otherwise, just cancel the cancellation.
const state = resend ? const state = resend ?
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND : RoomKeyRequestState.CancellationPendingAndWillResend :
ROOM_KEY_REQUEST_STATES.SENT; RoomKeyRequestState.Sent;
await this._cryptoStore.updateOutgoingRoomKeyRequest( await this.cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING, { req.requestId, RoomKeyRequestState.CancellationPending, {
state, state,
cancellationTxnId: this._baseApis.makeTxnId(), cancellationTxnId: this.baseApis.makeTxnId(),
}, },
); );
break; break;
}
case ROOM_KEY_REQUEST_STATES.SENT: {
// a request has already been sent. If we don't want to
// resend, then do nothing. If we do want to, then cancel the
// existing request and send a new one.
if (resend) {
const state =
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND;
const updatedReq =
await this._cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.SENT, {
state,
cancellationTxnId: this._baseApis.makeTxnId(),
// need to use a new transaction ID so that
// the request gets sent
requestTxnId: this._baseApis.makeTxnId(),
},
);
if (!updatedReq) {
// updateOutgoingRoomKeyRequest couldn't find the request
// in state ROOM_KEY_REQUEST_STATES.SENT, so we must have
// raced with another tab to mark the request cancelled.
// Try again, to make sure the request is resent.
return await this.queueRoomKeyRequest(
requestBody, recipients, resend,
);
}
// We don't want to wait for the timer, so we send it
// immediately. (We might actually end up racing with the timer,
// but that's ok: even if we make the request twice, we'll do it
// with the same transaction_id, so only one message will get
// sent).
//
// (We also don't want to wait for the response from the server
// here, as it will slow down processing of received keys if we
// do.)
try {
await this._sendOutgoingRoomKeyRequestCancellation(
updatedReq,
true,
);
} catch (e) {
logger.error(
"Error sending room key request cancellation;"
+ " will retry later.", e,
);
}
// The request has transitioned from
// CANCELLATION_PENDING_AND_WILL_RESEND to UNSENT. We
// still need to resend the request which is now UNSENT, so
// start the timer if it isn't already started.
} }
break; case RoomKeyRequestState.Sent: {
} // a request has already been sent. If we don't want to
default: // resend, then do nothing. If we do want to, then cancel the
throw new Error('unhandled state: ' + req.state); // existing request and send a new one.
if (resend) {
const state =
RoomKeyRequestState.CancellationPendingAndWillResend;
const updatedReq =
await this.cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, RoomKeyRequestState.Sent, {
state,
cancellationTxnId: this.baseApis.makeTxnId(),
// need to use a new transaction ID so that
// the request gets sent
requestTxnId: this.baseApis.makeTxnId(),
},
);
if (!updatedReq) {
// updateOutgoingRoomKeyRequest couldn't find the request
// in state ROOM_KEY_REQUEST_STATES.SENT, so we must have
// raced with another tab to mark the request cancelled.
// Try again, to make sure the request is resent.
return await this.queueRoomKeyRequest(
requestBody, recipients, resend,
);
}
// We don't want to wait for the timer, so we send it
// immediately. (We might actually end up racing with the timer,
// but that's ok: even if we make the request twice, we'll do it
// with the same transaction_id, so only one message will get
// sent).
//
// (We also don't want to wait for the response from the server
// here, as it will slow down processing of received keys if we
// do.)
try {
await this.sendOutgoingRoomKeyRequestCancellation(
updatedReq,
true,
);
} catch (e) {
logger.error(
"Error sending room key request cancellation;"
+ " will retry later.", e,
);
}
// The request has transitioned from
// CANCELLATION_PENDING_AND_WILL_RESEND to UNSENT. We
// still need to resend the request which is now UNSENT, so
// start the timer if it isn't already started.
}
break;
}
default:
throw new Error('unhandled state: ' + req.state);
} }
} }
} }
@@ -232,8 +237,8 @@ export class OutgoingRoomKeyRequestManager {
* @returns {Promise} resolves when the request has been updated in our * @returns {Promise} resolves when the request has been updated in our
* pending list. * pending list.
*/ */
cancelRoomKeyRequest(requestBody) { public cancelRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<void> {
return this._cryptoStore.getOutgoingRoomKeyRequest( return this.cryptoStore.getOutgoingRoomKeyRequest(
requestBody, requestBody,
).then((req) => { ).then((req) => {
if (!req) { if (!req) {
@@ -241,12 +246,12 @@ export class OutgoingRoomKeyRequestManager {
return; return;
} }
switch (req.state) { switch (req.state) {
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING: case RoomKeyRequestState.CancellationPending:
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND: case RoomKeyRequestState.CancellationPendingAndWillResend:
// nothing to do here // nothing to do here
return; return;
case ROOM_KEY_REQUEST_STATES.UNSENT: case RoomKeyRequestState.Unsent:
// just delete it // just delete it
// FIXME: ghahah we may have attempted to send it, and // FIXME: ghahah we may have attempted to send it, and
@@ -258,16 +263,16 @@ export class OutgoingRoomKeyRequestManager {
'deleting unnecessary room key request for ' + 'deleting unnecessary room key request for ' +
stringifyRequestBody(requestBody), stringifyRequestBody(requestBody),
); );
return this._cryptoStore.deleteOutgoingRoomKeyRequest( return this.cryptoStore.deleteOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.UNSENT, req.requestId, RoomKeyRequestState.Unsent,
); );
case ROOM_KEY_REQUEST_STATES.SENT: { case RoomKeyRequestState.Sent: {
// send a cancellation. // send a cancellation.
return this._cryptoStore.updateOutgoingRoomKeyRequest( return this.cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.SENT, { req.requestId, RoomKeyRequestState.Sent, {
state: ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING, state: RoomKeyRequestState.CancellationPending,
cancellationTxnId: this._baseApis.makeTxnId(), cancellationTxnId: this.baseApis.makeTxnId(),
}, },
).then((updatedReq) => { ).then((updatedReq) => {
if (!updatedReq) { if (!updatedReq) {
@@ -294,14 +299,14 @@ export class OutgoingRoomKeyRequestManager {
// (We also don't want to wait for the response from the server // (We also don't want to wait for the response from the server
// here, as it will slow down processing of received keys if we // here, as it will slow down processing of received keys if we
// do.) // do.)
this._sendOutgoingRoomKeyRequestCancellation( this.sendOutgoingRoomKeyRequestCancellation(
updatedReq, updatedReq,
).catch((e) => { ).catch((e) => {
logger.error( logger.error(
"Error sending room key request cancellation;" "Error sending room key request cancellation;"
+ " will retry later.", e, + " will retry later.", e,
); );
this._startTimer(); this.startTimer();
}); });
}); });
} }
@@ -320,10 +325,8 @@ export class OutgoingRoomKeyRequestManager {
* @return {Promise} resolves to a list of all the * @return {Promise} resolves to a list of all the
* {@link module:crypto/store/base~OutgoingRoomKeyRequest} * {@link module:crypto/store/base~OutgoingRoomKeyRequest}
*/ */
getOutgoingSentRoomKeyRequest(userId, deviceId) { public getOutgoingSentRoomKeyRequest(userId: string, deviceId: string): OutgoingRoomKeyRequest[] {
return this._cryptoStore.getOutgoingRoomKeyRequestsByTarget( return this.cryptoStore.getOutgoingRoomKeyRequestsByTarget(userId, deviceId, [RoomKeyRequestState.Sent]);
userId, deviceId, [ROOM_KEY_REQUEST_STATES.SENT],
);
} }
/** /**
@@ -333,29 +336,27 @@ export class OutgoingRoomKeyRequestManager {
* For example, after initialization or self-verification. * For example, after initialization or self-verification.
* @return {Promise} An array of `queueRoomKeyRequest` outputs. * @return {Promise} An array of `queueRoomKeyRequest` outputs.
*/ */
async cancelAndResendAllOutgoingRequests() { public async cancelAndResendAllOutgoingRequests(): Promise<void[]> {
const outgoings = await this._cryptoStore.getAllOutgoingRoomKeyRequestsByState( const outgoings = await this.cryptoStore.getAllOutgoingRoomKeyRequestsByState(RoomKeyRequestState.Sent);
ROOM_KEY_REQUEST_STATES.SENT,
);
return Promise.all(outgoings.map(({ requestBody, recipients }) => return Promise.all(outgoings.map(({ requestBody, recipients }) =>
this.queueRoomKeyRequest(requestBody, recipients, true))); this.queueRoomKeyRequest(requestBody, recipients, true)));
} }
// start the background timer to send queued requests, if the timer isn't // start the background timer to send queued requests, if the timer isn't
// already running // already running
_startTimer() { private startTimer(): void {
if (this._sendOutgoingRoomKeyRequestsTimer) { if (this.sendOutgoingRoomKeyRequestsTimer) {
return; return;
} }
const startSendingOutgoingRoomKeyRequests = () => { const startSendingOutgoingRoomKeyRequests = () => {
if (this._sendOutgoingRoomKeyRequestsRunning) { if (this.sendOutgoingRoomKeyRequestsRunning) {
throw new Error("RoomKeyRequestSend already in progress!"); throw new Error("RoomKeyRequestSend already in progress!");
} }
this._sendOutgoingRoomKeyRequestsRunning = true; this.sendOutgoingRoomKeyRequestsRunning = true;
this._sendOutgoingRoomKeyRequests().finally(() => { this.sendOutgoingRoomKeyRequests().finally(() => {
this._sendOutgoingRoomKeyRequestsRunning = false; this.sendOutgoingRoomKeyRequestsRunning = false;
}).catch((e) => { }).catch((e) => {
// this should only happen if there is an indexeddb error, // this should only happen if there is an indexeddb error,
// in which case we're a bit stuffed anyway. // in which case we're a bit stuffed anyway.
@@ -365,7 +366,7 @@ export class OutgoingRoomKeyRequestManager {
}); });
}; };
this._sendOutgoingRoomKeyRequestsTimer = global.setTimeout( this.sendOutgoingRoomKeyRequestsTimer = global.setTimeout(
startSendingOutgoingRoomKeyRequests, startSendingOutgoingRoomKeyRequests,
SEND_KEY_REQUESTS_DELAY_MS, SEND_KEY_REQUESTS_DELAY_MS,
); );
@@ -374,47 +375,47 @@ export class OutgoingRoomKeyRequestManager {
// look for and send any queued requests. Runs itself recursively until // look for and send any queued requests. Runs itself recursively until
// there are no more requests, or there is an error (in which case, the // there are no more requests, or there is an error (in which case, the
// timer will be restarted before the promise resolves). // timer will be restarted before the promise resolves).
_sendOutgoingRoomKeyRequests() { private sendOutgoingRoomKeyRequests(): Promise<void> {
if (!this._clientRunning) { if (!this.clientRunning) {
this._sendOutgoingRoomKeyRequestsTimer = null; this.sendOutgoingRoomKeyRequestsTimer = null;
return Promise.resolve(); return Promise.resolve();
} }
return this._cryptoStore.getOutgoingRoomKeyRequestByState([ return this.cryptoStore.getOutgoingRoomKeyRequestByState([
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING, RoomKeyRequestState.CancellationPending,
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND, RoomKeyRequestState.CancellationPendingAndWillResend,
ROOM_KEY_REQUEST_STATES.UNSENT, RoomKeyRequestState.Unsent,
]).then((req) => { ]).then((req: OutgoingRoomKeyRequest) => {
if (!req) { if (!req) {
this._sendOutgoingRoomKeyRequestsTimer = null; this.sendOutgoingRoomKeyRequestsTimer = null;
return; return;
} }
let prom; let prom;
switch (req.state) { switch (req.state) {
case ROOM_KEY_REQUEST_STATES.UNSENT: case RoomKeyRequestState.Unsent:
prom = this._sendOutgoingRoomKeyRequest(req); prom = this.sendOutgoingRoomKeyRequest(req);
break; break;
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING: case RoomKeyRequestState.CancellationPending:
prom = this._sendOutgoingRoomKeyRequestCancellation(req); prom = this.sendOutgoingRoomKeyRequestCancellation(req);
break; break;
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND: case RoomKeyRequestState.CancellationPendingAndWillResend:
prom = this._sendOutgoingRoomKeyRequestCancellation(req, true); prom = this.sendOutgoingRoomKeyRequestCancellation(req, true);
break; break;
} }
return prom.then(() => { return prom.then(() => {
// go around the loop again // go around the loop again
return this._sendOutgoingRoomKeyRequests(); return this.sendOutgoingRoomKeyRequests();
}).catch((e) => { }).catch((e) => {
logger.error("Error sending room key request; will retry later.", e); logger.error("Error sending room key request; will retry later.", e);
this._sendOutgoingRoomKeyRequestsTimer = null; this.sendOutgoingRoomKeyRequestsTimer = null;
}); });
}); });
} }
// given a RoomKeyRequest, send it and update the request record // given a RoomKeyRequest, send it and update the request record
_sendOutgoingRoomKeyRequest(req) { private sendOutgoingRoomKeyRequest(req: OutgoingRoomKeyRequest): Promise<void> {
logger.log( logger.log(
`Requesting keys for ${stringifyRequestBody(req.requestBody)}` + `Requesting keys for ${stringifyRequestBody(req.requestBody)}` +
` from ${stringifyRecipientList(req.recipients)}` + ` from ${stringifyRecipientList(req.recipients)}` +
@@ -423,24 +424,24 @@ export class OutgoingRoomKeyRequestManager {
const requestMessage = { const requestMessage = {
action: "request", action: "request",
requesting_device_id: this._deviceId, requesting_device_id: this.deviceId,
request_id: req.requestId, request_id: req.requestId,
body: req.requestBody, body: req.requestBody,
}; };
return this._sendMessageToDevices( return this.sendMessageToDevices(
requestMessage, req.recipients, req.requestTxnId || req.requestId, requestMessage, req.recipients, req.requestTxnId || req.requestId,
).then(() => { ).then(() => {
return this._cryptoStore.updateOutgoingRoomKeyRequest( return this.cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.UNSENT, req.requestId, RoomKeyRequestState.Unsent,
{ state: ROOM_KEY_REQUEST_STATES.SENT }, { state: RoomKeyRequestState.Sent },
); );
}); });
} }
// Given a RoomKeyRequest, cancel it and delete the request record unless // Given a RoomKeyRequest, cancel it and delete the request record unless
// andResend is set, in which case transition to UNSENT. // andResend is set, in which case transition to UNSENT.
_sendOutgoingRoomKeyRequestCancellation(req, andResend) { private sendOutgoingRoomKeyRequestCancellation(req: OutgoingRoomKeyRequest, andResend = false): Promise<void> {
logger.log( logger.log(
`Sending cancellation for key request for ` + `Sending cancellation for key request for ` +
`${stringifyRequestBody(req.requestBody)} to ` + `${stringifyRequestBody(req.requestBody)} to ` +
@@ -450,30 +451,30 @@ export class OutgoingRoomKeyRequestManager {
const requestMessage = { const requestMessage = {
action: "request_cancellation", action: "request_cancellation",
requesting_device_id: this._deviceId, requesting_device_id: this.deviceId,
request_id: req.requestId, request_id: req.requestId,
}; };
return this._sendMessageToDevices( return this.sendMessageToDevices(
requestMessage, req.recipients, req.cancellationTxnId, requestMessage, req.recipients, req.cancellationTxnId,
).then(() => { ).then(() => {
if (andResend) { if (andResend) {
// We want to resend, so transition to UNSENT // We want to resend, so transition to UNSENT
return this._cryptoStore.updateOutgoingRoomKeyRequest( return this.cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, req.requestId,
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND, RoomKeyRequestState.CancellationPendingAndWillResend,
{ state: ROOM_KEY_REQUEST_STATES.UNSENT }, { state: RoomKeyRequestState.Unsent },
); );
} }
return this._cryptoStore.deleteOutgoingRoomKeyRequest( return this.cryptoStore.deleteOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING, req.requestId, RoomKeyRequestState.CancellationPending,
); );
}); });
} }
// send a RoomKeyRequest to a list of recipients // send a RoomKeyRequest to a list of recipients
_sendMessageToDevices(message, recipients, txnId) { private sendMessageToDevices(message, recipients, txnId: string): Promise<{}> {
const contentMap = {}; const contentMap: Record<string, Record<string, Record<string, any>>> = {};
for (const recip of recipients) { for (const recip of recipients) {
if (!contentMap[recip.userId]) { if (!contentMap[recip.userId]) {
contentMap[recip.userId] = {}; contentMap[recip.userId] = {};
@@ -481,9 +482,7 @@ export class OutgoingRoomKeyRequestManager {
contentMap[recip.userId][recip.deviceId] = message; contentMap[recip.userId][recip.deviceId] = message;
} }
return this._baseApis.sendToDevice( return this.baseApis.sendToDevice(EventType.RoomKeyRequest, contentMap, txnId);
'm.room_key_request', contentMap, txnId,
);
} }
} }

View File

@@ -25,7 +25,7 @@ const subtleCrypto = (typeof window !== "undefined" && window.crypto) ?
// salt for HKDF, with 8 bytes of zeros // salt for HKDF, with 8 bytes of zeros
const zeroSalt = new Uint8Array(8); const zeroSalt = new Uint8Array(8);
interface IEncryptedPayload { export interface IEncryptedPayload {
iv: string; iv: string;
ciphertext: string; ciphertext: string;
mac: string; mac: string;

View File

@@ -15,7 +15,7 @@ limitations under the License.
*/ */
import { DeviceInfo } from "./deviceinfo"; import { DeviceInfo } from "./deviceinfo";
import { IKeyBackupVersion } from "./keybackup"; import { IKeyBackupInfo } from "./keybackup";
import { ISecretStorageKeyInfo } from "../matrix"; import { ISecretStorageKeyInfo } from "../matrix";
// TODO: Merge this with crypto.js once converted // TODO: Merge this with crypto.js once converted
@@ -85,7 +85,7 @@ export interface ICreateSecretStorageOpts {
* The current key backup object. If passed, * The current key backup object. If passed,
* the passphrase and recovery key from this backup will be used. * the passphrase and recovery key from this backup will be used.
*/ */
keyBackupInfo?: IKeyBackupVersion; keyBackupInfo?: IKeyBackupInfo;
/** /**
* If true, a new key backup version will be * If true, a new key backup version will be

View File

@@ -29,11 +29,11 @@ import { keyFromPassphrase } from './key_passphrase';
import { sleep } from "../utils"; import { sleep } from "../utils";
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import { encodeRecoveryKey } from './recoverykey'; import { encodeRecoveryKey } from './recoverykey';
import { IKeyBackupVersion } from "./keybackup"; import { IKeyBackupInfo } from "./keybackup";
const KEY_BACKUP_KEYS_PER_REQUEST = 200; const KEY_BACKUP_KEYS_PER_REQUEST = 200;
type AuthData = IKeyBackupVersion["auth_data"]; type AuthData = IKeyBackupInfo["auth_data"];
type SigInfo = { type SigInfo = {
deviceId: string, deviceId: string,
@@ -49,7 +49,7 @@ export type TrustInfo = {
}; };
export interface IKeyBackupCheck { export interface IKeyBackupCheck {
backupInfo: IKeyBackupVersion; backupInfo: IKeyBackupInfo;
trustInfo: TrustInfo; trustInfo: TrustInfo;
} }
@@ -90,7 +90,7 @@ interface BackupAlgorithm {
*/ */
export class BackupManager { export class BackupManager {
private algorithm: BackupAlgorithm | undefined; private algorithm: BackupAlgorithm | undefined;
public backupInfo: IKeyBackupVersion | undefined; // The info dict from /room_keys/version public backupInfo: IKeyBackupInfo | undefined; // The info dict from /room_keys/version
public checkedForBackup: boolean; // Have we checked the server for a backup we can use? public checkedForBackup: boolean; // Have we checked the server for a backup we can use?
private sendingBackups: boolean; // Are we currently sending backups? private sendingBackups: boolean; // Are we currently sending backups?
constructor(private readonly baseApis: MatrixClient, public readonly getKey: GetKey) { constructor(private readonly baseApis: MatrixClient, public readonly getKey: GetKey) {
@@ -102,7 +102,7 @@ export class BackupManager {
return this.backupInfo && this.backupInfo.version; return this.backupInfo && this.backupInfo.version;
} }
public static async makeAlgorithm(info: IKeyBackupVersion, getKey: GetKey): Promise<BackupAlgorithm> { public static async makeAlgorithm(info: IKeyBackupInfo, getKey: GetKey): Promise<BackupAlgorithm> {
const Algorithm = algorithmsByName[info.algorithm]; const Algorithm = algorithmsByName[info.algorithm];
if (!Algorithm) { if (!Algorithm) {
throw new Error("Unknown backup algorithm"); throw new Error("Unknown backup algorithm");
@@ -110,7 +110,7 @@ export class BackupManager {
return await Algorithm.init(info.auth_data, getKey); return await Algorithm.init(info.auth_data, getKey);
} }
public async enableKeyBackup(info: IKeyBackupVersion): Promise<void> { public async enableKeyBackup(info: IKeyBackupInfo): Promise<void> {
this.backupInfo = info; this.backupInfo = info;
if (this.algorithm) { if (this.algorithm) {
this.algorithm.free(); this.algorithm.free();
@@ -166,7 +166,7 @@ export class BackupManager {
}; };
} }
public async createKeyBackupVersion(info: IKeyBackupVersion): Promise<void> { public async createKeyBackupVersion(info: IKeyBackupInfo): Promise<void> {
this.algorithm = await BackupManager.makeAlgorithm(info, this.getKey); this.algorithm = await BackupManager.makeAlgorithm(info, this.getKey);
} }
@@ -183,7 +183,7 @@ export class BackupManager {
this.checkedForBackup = true; this.checkedForBackup = true;
return null; return null;
} }
let backupInfo: IKeyBackupVersion; let backupInfo: IKeyBackupInfo;
try { try {
backupInfo = await this.baseApis.getKeyBackupVersion(); backupInfo = await this.baseApis.getKeyBackupVersion();
} catch (e) { } catch (e) {
@@ -260,7 +260,7 @@ export class BackupManager {
* ] * ]
* } * }
*/ */
public async isKeyBackupTrusted(backupInfo: IKeyBackupVersion): Promise<TrustInfo> { public async isKeyBackupTrusted(backupInfo: IKeyBackupInfo): Promise<TrustInfo> {
const ret = { const ret = {
usable: false, usable: false,
trusted_locally: false, trusted_locally: false,

View File

@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { ISignatures } from "../@types/signed";
/** /**
* @module crypto/deviceinfo * @module crypto/deviceinfo
*/ */

View File

@@ -52,10 +52,11 @@ import { IStore } from "../store";
import { Room } from "../models/room"; import { Room } from "../models/room";
import { RoomMember } from "../models/room-member"; import { RoomMember } from "../models/room-member";
import { MatrixEvent } from "../models/event"; import { MatrixEvent } from "../models/event";
import { MatrixClient, IKeysUploadResponse, SessionStore, CryptoStore } from "../client"; import { MatrixClient, IKeysUploadResponse, SessionStore, CryptoStore, ISignedKey } from "../client";
import type { EncryptionAlgorithm, DecryptionAlgorithm } from "./algorithms/base"; import type { EncryptionAlgorithm, DecryptionAlgorithm } from "./algorithms/base";
import type { RoomList } from "./RoomList"; import type { RoomList } from "./RoomList";
import { IRecoveryKey, IEncryptedEventInfo } from "./api"; import { IRecoveryKey, IEncryptedEventInfo } from "./api";
import { IKeyBackupInfo } from "./keybackup";
const DeviceVerification = DeviceInfo.DeviceVerification; const DeviceVerification = DeviceInfo.DeviceVerification;
@@ -91,7 +92,7 @@ interface IInitOpts {
export interface IBootstrapCrossSigningOpts { export interface IBootstrapCrossSigningOpts {
setupNewCrossSigning?: boolean; setupNewCrossSigning?: boolean;
authUploadDeviceSigningKeys?(makeRequest: (authData: any) => void): Promise<void>; authUploadDeviceSigningKeys?(makeRequest: (authData: any) => void): Promise<{}>;
} }
interface IBootstrapSecretStorageOpts { interface IBootstrapSecretStorageOpts {
@@ -111,7 +112,7 @@ interface IRoomKey {
algorithm: string; algorithm: string;
} }
interface IRoomKeyRequestBody extends IRoomKey { export interface IRoomKeyRequestBody extends IRoomKey {
session_id: string; session_id: string;
sender_key: string sender_key: string
} }
@@ -157,7 +158,7 @@ interface ISyncDeviceLists {
left: string[]; left: string[];
} }
interface IRoomKeyRequestRecipient { export interface IRoomKeyRequestRecipient {
userId: string; userId: string;
deviceId: string; deviceId: string;
} }
@@ -276,7 +277,7 @@ export class Crypto extends EventEmitter {
* or a class that implements a verification method. * or a class that implements a verification method.
*/ */
constructor( constructor(
private readonly baseApis: MatrixClient, public readonly baseApis: MatrixClient,
public readonly sessionStore: SessionStore, public readonly sessionStore: SessionStore,
private readonly userId: string, private readonly userId: string,
private readonly deviceId: string, private readonly deviceId: string,
@@ -622,7 +623,7 @@ export class Crypto extends EventEmitter {
// Cross-sign own device // Cross-sign own device
const device = this.deviceList.getStoredDevice(this.userId, this.deviceId); const device = this.deviceList.getStoredDevice(this.userId, this.deviceId);
const deviceSignature = await crossSigningInfo.signDevice(this.userId, device); const deviceSignature = await crossSigningInfo.signDevice(this.userId, device) as ISignedKey;
builder.addKeySignature(this.userId, this.deviceId, deviceSignature); builder.addKeySignature(this.userId, this.deviceId, deviceSignature);
// Sign message key backup with cross-signing master key // Sign message key backup with cross-signing master key
@@ -932,7 +933,7 @@ export class Crypto extends EventEmitter {
await secretStorage.store("m.megolm_backup.v1", olmlib.encodeBase64(privateKey)); await secretStorage.store("m.megolm_backup.v1", olmlib.encodeBase64(privateKey));
// create keyBackupInfo object to add to builder // create keyBackupInfo object to add to builder
const data = { const data: IKeyBackupInfo = {
algorithm: info.algorithm, algorithm: info.algorithm,
auth_data: info.auth_data, auth_data: info.auth_data,
}; };
@@ -2843,8 +2844,8 @@ export class Crypto extends EventEmitter {
* Re-send any outgoing key requests, eg after verification * Re-send any outgoing key requests, eg after verification
* @returns {Promise} * @returns {Promise}
*/ */
public cancelAndResendAllOutgoingKeyRequests(): Promise<void> { public async cancelAndResendAllOutgoingKeyRequests(): Promise<void> {
return this.outgoingRoomKeyRequestManager.cancelAndResendAllOutgoingRequests(); await this.outgoingRoomKeyRequestManager.cancelAndResendAllOutgoingRequests();
} }
/** /**
@@ -3291,9 +3292,7 @@ export class Crypto extends EventEmitter {
// it. This won't always be the case though so we need to re-send any that have already been sent // it. This won't always be the case though so we need to re-send any that have already been sent
// to avoid races. // to avoid races.
const requestsToResend = const requestsToResend =
await this.outgoingRoomKeyRequestManager.getOutgoingSentRoomKeyRequest( await this.outgoingRoomKeyRequestManager.getOutgoingSentRoomKeyRequest(sender, device.deviceId);
sender, device.deviceId,
);
for (const keyReq of requestsToResend) { for (const keyReq of requestsToResend) {
this.requestRoomKey(keyReq.requestBody, keyReq.recipients, true); this.requestRoomKey(keyReq.requestBody, keyReq.recipients, true);
} }

View File

@@ -32,7 +32,7 @@ export interface IKeyBackupRoomSessions {
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
export interface IKeyBackupVersion { export interface IKeyBackupInfo {
algorithm: string; algorithm: string;
auth_data: { auth_data: {
public_key: string; public_key: string;
@@ -41,9 +41,9 @@ export interface IKeyBackupVersion {
private_key_iterations: number; private_key_iterations: number;
private_key_bits?: number; private_key_bits?: number;
}; };
count: number; count?: number;
etag: string; etag?: string;
version: string; // number contained within version?: string; // number contained within
} }
/* eslint-enable camelcase */ /* eslint-enable camelcase */

View File

@@ -566,7 +566,7 @@ export function encodeBase64(uint8Array: ArrayBuffer | Uint8Array): string {
* @param {Uint8Array} uint8Array The data to encode. * @param {Uint8Array} uint8Array The data to encode.
* @return {string} The unpadded base64. * @return {string} The unpadded base64.
*/ */
export function encodeUnpaddedBase64(uint8Array: Uint8Array): string { export function encodeUnpaddedBase64(uint8Array: ArrayBuffer | Uint8Array): string {
return encodeBase64(uint8Array).replace(/=+$/g, ''); return encodeBase64(uint8Array).replace(/=+$/g, '');
} }

View File

@@ -10,6 +10,9 @@
* @interface CryptoStore * @interface CryptoStore
*/ */
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index";
import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager";
/** /**
* Represents an outgoing room key request * Represents an outgoing room key request
* *
@@ -32,3 +35,11 @@
* @property {Number} state current state of this request (states are defined * @property {Number} state current state of this request (states are defined
* in {@link module:crypto/OutgoingRoomKeyRequestManager~ROOM_KEY_REQUEST_STATES}) * in {@link module:crypto/OutgoingRoomKeyRequestManager~ROOM_KEY_REQUEST_STATES})
*/ */
export interface OutgoingRoomKeyRequest {
requestId: string;
requestTxnId?: string;
cancellationTxnId?: string;
recipients: IRoomKeyRequestRecipient[];
requestBody: IRoomKeyRequestBody;
state: RoomKeyRequestState;
}