You've already forked matrix-js-sdk
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:
@@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|
||||||
|
|||||||
@@ -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, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user