You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-06 12:02:40 +03:00
Convert crypto/store/* to Typescript
This commit is contained in:
@@ -74,3 +74,11 @@ export enum HistoryVisibility {
|
||||
Shared = "shared",
|
||||
WorldReadable = "world_readable",
|
||||
}
|
||||
|
||||
// XXX move to OlmDevice when converted
|
||||
export interface InboundGroupSessionData {
|
||||
room_id: string; // eslint-disable-line camelcase
|
||||
session: string;
|
||||
keysClaimed: Record<string, string>;
|
||||
forwardingCurve25519KeyChain: string[];
|
||||
}
|
||||
|
@@ -146,10 +146,10 @@ import { ISynapseAdminDeactivateResponse, ISynapseAdminWhoisResponse } from "./@
|
||||
import { ISpaceSummaryEvent, ISpaceSummaryRoom } from "./@types/spaces";
|
||||
import { IPusher, IPusherRequest, IPushRules, PushRuleAction, PushRuleKind, RuleId } from "./@types/PushRules";
|
||||
import { IThreepid } from "./@types/threepids";
|
||||
import { CryptoStore } from "./crypto/store/base";
|
||||
|
||||
export type Store = IStore;
|
||||
export type SessionStore = WebStorageSessionStore;
|
||||
export type CryptoStore = MemoryCryptoStore | LocalStorageCryptoStore | IndexedDBCryptoStore;
|
||||
|
||||
export type Callback = (err: Error | any | null, data?: any) => void;
|
||||
export type ResetTimelineCallback = (roomId: string) => boolean;
|
||||
|
@@ -28,10 +28,11 @@ import { decryptAES, encryptAES } from './aes';
|
||||
import { PkSigning } from "@matrix-org/olm";
|
||||
import { DeviceInfo } from "./deviceinfo";
|
||||
import { SecretStorage } from "./SecretStorage";
|
||||
import { CryptoStore, ICrossSigningKey, ISignedKey, MatrixClient } from "../client";
|
||||
import { ICrossSigningKey, ISignedKey, MatrixClient } from "../client";
|
||||
import { OlmDevice } from "./OlmDevice";
|
||||
import { ICryptoCallbacks } from "../matrix";
|
||||
import { ISignatures } from "../@types/signed";
|
||||
import { CryptoStore } from "./store/base";
|
||||
|
||||
const KEY_REQUEST_TIMEOUT_MS = 1000 * 60;
|
||||
|
||||
@@ -47,6 +48,12 @@ export interface ICacheCallbacks {
|
||||
storeCrossSigningKeyCache?(type: string, key: Uint8Array): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ICrossSigningInfo {
|
||||
keys: Record<string, ICrossSigningKey>;
|
||||
firstUse: boolean;
|
||||
crossSigningVerifiedBefore: boolean;
|
||||
}
|
||||
|
||||
export class CrossSigningInfo extends EventEmitter {
|
||||
public keys: Record<string, ICrossSigningKey> = {};
|
||||
public firstUse = true;
|
||||
@@ -75,7 +82,7 @@ export class CrossSigningInfo extends EventEmitter {
|
||||
super();
|
||||
}
|
||||
|
||||
public static fromStorage(obj: object, userId: string): CrossSigningInfo {
|
||||
public static fromStorage(obj: ICrossSigningInfo, userId: string): CrossSigningInfo {
|
||||
const res = new CrossSigningInfo(userId);
|
||||
for (const prop in obj) {
|
||||
if (obj.hasOwnProperty(prop)) {
|
||||
@@ -85,7 +92,7 @@ export class CrossSigningInfo extends EventEmitter {
|
||||
return res;
|
||||
}
|
||||
|
||||
public toStorage(): object {
|
||||
public toStorage(): ICrossSigningInfo {
|
||||
return {
|
||||
keys: this.keys,
|
||||
firstUse: this.firstUse,
|
||||
|
@@ -24,12 +24,13 @@ import { EventEmitter } from 'events';
|
||||
|
||||
import { logger } from '../logger';
|
||||
import { DeviceInfo, IDevice } from './deviceinfo';
|
||||
import { CrossSigningInfo } from './CrossSigning';
|
||||
import { CrossSigningInfo, ICrossSigningInfo } from './CrossSigning';
|
||||
import * as olmlib from './olmlib';
|
||||
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
|
||||
import { chunkPromises, defer, IDeferred, sleep } from '../utils';
|
||||
import { MatrixClient, CryptoStore } from "../client";
|
||||
import { MatrixClient } from "../client";
|
||||
import { OlmDevice } from "./OlmDevice";
|
||||
import { CryptoStore } from "./store/base";
|
||||
|
||||
/* State transition diagram for DeviceList.deviceTrackingStatus
|
||||
*
|
||||
@@ -52,7 +53,7 @@ import { OlmDevice } from "./OlmDevice";
|
||||
*/
|
||||
|
||||
// constants for DeviceList.deviceTrackingStatus
|
||||
enum TrackingStatus {
|
||||
export enum TrackingStatus {
|
||||
NotTracked,
|
||||
PendingDownload,
|
||||
DownloadInProgress,
|
||||
@@ -65,32 +66,22 @@ export type DeviceInfoMap = Record<string, Record<string, DeviceInfo>>;
|
||||
* @alias module:crypto/DeviceList
|
||||
*/
|
||||
export class DeviceList extends EventEmitter {
|
||||
// userId -> {
|
||||
// deviceId -> {
|
||||
// [device info]
|
||||
// }
|
||||
// }
|
||||
private devices: Record<string, Record<string, IDevice>> = {};
|
||||
private devices: { [userId: string]: { [deviceId: string]: IDevice } } = {};
|
||||
|
||||
// userId -> {
|
||||
// [key info]
|
||||
// }
|
||||
public crossSigningInfo: Record<string, object> = {};
|
||||
public crossSigningInfo: { [userId: string]: ICrossSigningInfo } = {};
|
||||
|
||||
// map of identity keys to the user who owns it
|
||||
private userByIdentityKey: Record<string, string> = {};
|
||||
|
||||
// which users we are tracking device status for.
|
||||
// userId -> TRACKING_STATUS_*
|
||||
private deviceTrackingStatus: Record<string, TrackingStatus> = {}; // loaded from storage in load()
|
||||
private deviceTrackingStatus: { [userId: string]: TrackingStatus } = {}; // loaded from storage in load()
|
||||
|
||||
// The 'next_batch' sync token at the point the data was written,
|
||||
// ie. a token representing the point immediately after the
|
||||
// moment represented by the snapshot in the db.
|
||||
private syncToken: string = null;
|
||||
|
||||
// userId -> promise
|
||||
private keyDownloadsInProgressByUser: Record<string, Promise<void>> = {};
|
||||
private keyDownloadsInProgressByUser: { [userId: string]: Promise<void> } = {};
|
||||
|
||||
// Set whenever changes are made other than setting the sync token
|
||||
private dirty = false;
|
||||
@@ -375,7 +366,7 @@ export class DeviceList extends EventEmitter {
|
||||
return CrossSigningInfo.fromStorage(this.crossSigningInfo[userId], userId);
|
||||
}
|
||||
|
||||
public storeCrossSigningForUser(userId: string, info: CrossSigningInfo): void {
|
||||
public storeCrossSigningForUser(userId: string, info: ICrossSigningInfo): void {
|
||||
this.crossSigningInfo[userId] = info;
|
||||
this.dirty = true;
|
||||
}
|
||||
@@ -603,7 +594,7 @@ export class DeviceList extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
public setRawStoredCrossSigningForUser(userId: string, info: object): void {
|
||||
public setRawStoredCrossSigningForUser(userId: string, info: ICrossSigningInfo): void {
|
||||
this.crossSigningInfo[userId] = info;
|
||||
}
|
||||
|
||||
@@ -864,9 +855,7 @@ class DeviceListUpdateSerialiser {
|
||||
|
||||
crossSigning.setKeys(crossSigningResponse);
|
||||
|
||||
this.deviceList.setRawStoredCrossSigningForUser(
|
||||
userId, crossSigning.toStorage(),
|
||||
);
|
||||
this.deviceList.setRawStoredCrossSigningForUser(userId, crossSigning.toStorage());
|
||||
|
||||
// NB. Unlike most events in the js-sdk, this one is internal to the
|
||||
// js-sdk and is not re-emitted
|
||||
|
@@ -949,7 +949,7 @@ OlmDevice.prototype.getOutboundGroupSessionKey = function(sessionId) {
|
||||
* data stored in the session store about an inbound group session
|
||||
*
|
||||
* @typedef {Object} InboundGroupSessionData
|
||||
* @property {string} room_Id
|
||||
* @property {string} room_id
|
||||
* @property {string} session pickled Olm.InboundGroupSession
|
||||
* @property {Object<string, string>} keysClaimed
|
||||
* @property {Array<string>} forwardingCurve25519KeyChain Devices involved in forwarding
|
||||
|
@@ -15,9 +15,9 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { logger } from '../logger';
|
||||
import { CryptoStore, MatrixClient } from "../client";
|
||||
import { MatrixClient } from "../client";
|
||||
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "./index";
|
||||
import { OutgoingRoomKeyRequest } from './store/base';
|
||||
import { CryptoStore, OutgoingRoomKeyRequest } from './store/base';
|
||||
import { EventType } from "../@types/event";
|
||||
|
||||
/**
|
||||
@@ -263,9 +263,8 @@ export class OutgoingRoomKeyRequestManager {
|
||||
'deleting unnecessary room key request for ' +
|
||||
stringifyRequestBody(requestBody),
|
||||
);
|
||||
return this.cryptoStore.deleteOutgoingRoomKeyRequest(
|
||||
req.requestId, RoomKeyRequestState.Unsent,
|
||||
);
|
||||
return this.cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId, RoomKeyRequestState.Unsent)
|
||||
.then(); // match Promise<void> signature
|
||||
|
||||
case RoomKeyRequestState.Sent: {
|
||||
// send a cancellation.
|
||||
@@ -325,7 +324,7 @@ export class OutgoingRoomKeyRequestManager {
|
||||
* @return {Promise} resolves to a list of all the
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
|
||||
*/
|
||||
public getOutgoingSentRoomKeyRequest(userId: string, deviceId: string): OutgoingRoomKeyRequest[] {
|
||||
public getOutgoingSentRoomKeyRequest(userId: string, deviceId: string): Promise<OutgoingRoomKeyRequest[]> {
|
||||
return this.cryptoStore.getOutgoingRoomKeyRequestsByTarget(userId, deviceId, [RoomKeyRequestState.Sent]);
|
||||
}
|
||||
|
||||
@@ -415,7 +414,7 @@ export class OutgoingRoomKeyRequestManager {
|
||||
}
|
||||
|
||||
// given a RoomKeyRequest, send it and update the request record
|
||||
private sendOutgoingRoomKeyRequest(req: OutgoingRoomKeyRequest): Promise<void> {
|
||||
private async sendOutgoingRoomKeyRequest(req: OutgoingRoomKeyRequest): Promise<void> {
|
||||
logger.log(
|
||||
`Requesting keys for ${stringifyRequestBody(req.requestBody)}` +
|
||||
` from ${stringifyRecipientList(req.recipients)}` +
|
||||
@@ -429,19 +428,19 @@ export class OutgoingRoomKeyRequestManager {
|
||||
body: req.requestBody,
|
||||
};
|
||||
|
||||
return this.sendMessageToDevices(
|
||||
requestMessage, req.recipients, req.requestTxnId || req.requestId,
|
||||
).then(() => {
|
||||
return this.cryptoStore.updateOutgoingRoomKeyRequest(
|
||||
await this.sendMessageToDevices(requestMessage, req.recipients, req.requestTxnId || req.requestId);
|
||||
await this.cryptoStore.updateOutgoingRoomKeyRequest(
|
||||
req.requestId, RoomKeyRequestState.Unsent,
|
||||
{ state: RoomKeyRequestState.Sent },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Given a RoomKeyRequest, cancel it and delete the request record unless
|
||||
// andResend is set, in which case transition to UNSENT.
|
||||
private sendOutgoingRoomKeyRequestCancellation(req: OutgoingRoomKeyRequest, andResend = false): Promise<void> {
|
||||
private async sendOutgoingRoomKeyRequestCancellation(
|
||||
req: OutgoingRoomKeyRequest,
|
||||
andResend = false,
|
||||
): Promise<void> {
|
||||
logger.log(
|
||||
`Sending cancellation for key request for ` +
|
||||
`${stringifyRequestBody(req.requestBody)} to ` +
|
||||
@@ -455,21 +454,19 @@ export class OutgoingRoomKeyRequestManager {
|
||||
request_id: req.requestId,
|
||||
};
|
||||
|
||||
return this.sendMessageToDevices(
|
||||
requestMessage, req.recipients, req.cancellationTxnId,
|
||||
).then(() => {
|
||||
await this.sendMessageToDevices(requestMessage, req.recipients, req.cancellationTxnId);
|
||||
if (andResend) {
|
||||
// We want to resend, so transition to UNSENT
|
||||
return this.cryptoStore.updateOutgoingRoomKeyRequest(
|
||||
await this.cryptoStore.updateOutgoingRoomKeyRequest(
|
||||
req.requestId,
|
||||
RoomKeyRequestState.CancellationPendingAndWillResend,
|
||||
{ state: RoomKeyRequestState.Unsent },
|
||||
);
|
||||
}
|
||||
return this.cryptoStore.deleteOutgoingRoomKeyRequest(
|
||||
} else {
|
||||
await this.cryptoStore.deleteOutgoingRoomKeyRequest(
|
||||
req.requestId, RoomKeyRequestState.CancellationPending,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// send a RoomKeyRequest to a list of recipients
|
||||
|
@@ -20,8 +20,8 @@ limitations under the License.
|
||||
* Manages the list of encrypted rooms
|
||||
*/
|
||||
|
||||
import { CryptoStore } from './store/base';
|
||||
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
|
||||
import { CryptoStore } from "../client";
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
export interface IRoomEncryption {
|
||||
|
@@ -61,7 +61,7 @@ interface IBlockedMap {
|
||||
};
|
||||
}
|
||||
|
||||
interface IOlmDevice<T = DeviceInfo> {
|
||||
export interface IOlmDevice<T = DeviceInfo> {
|
||||
userId: string;
|
||||
deviceInfo: T;
|
||||
}
|
||||
|
@@ -59,12 +59,13 @@ import { IStore } from "../store";
|
||||
import { Room } from "../models/room";
|
||||
import { RoomMember } from "../models/room-member";
|
||||
import { MatrixEvent } from "../models/event";
|
||||
import { MatrixClient, IKeysUploadResponse, SessionStore, CryptoStore, ISignedKey } from "../client";
|
||||
import { MatrixClient, IKeysUploadResponse, SessionStore, ISignedKey } from "../client";
|
||||
import type { EncryptionAlgorithm, DecryptionAlgorithm } from "./algorithms/base";
|
||||
import type { IRoomEncryption, RoomList } from "./RoomList";
|
||||
import { IRecoveryKey, IEncryptedEventInfo } from "./api";
|
||||
import { IKeyBackupInfo } from "./keybackup";
|
||||
import { ISyncStateData } from "../sync";
|
||||
import { CryptoStore } from "./store/base";
|
||||
|
||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||
|
||||
@@ -1410,9 +1411,7 @@ export class Crypto extends EventEmitter {
|
||||
crossSigning.updateCrossSigningVerifiedBefore(
|
||||
this.checkUserTrust(userId).isCrossSigningVerified(),
|
||||
);
|
||||
this.deviceList.setRawStoredCrossSigningForUser(
|
||||
userId, crossSigning.toStorage(),
|
||||
);
|
||||
this.deviceList.setRawStoredCrossSigningForUser(userId, crossSigning.toStorage());
|
||||
}
|
||||
|
||||
this.emit("userTrustStatusChanged", userId, this.checkUserTrust(userId));
|
||||
|
@@ -1,5 +1,33 @@
|
||||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index";
|
||||
import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager";
|
||||
import { ICrossSigningKey } from "../../client";
|
||||
import { IOlmDevice } from "../algorithms/megolm";
|
||||
import { TrackingStatus } from "../DeviceList";
|
||||
import { IRoomEncryption } from "../RoomList";
|
||||
import { IDevice } from "../deviceinfo";
|
||||
import { ICrossSigningInfo } from "../CrossSigning";
|
||||
import { PrefixedLogger } from "../../logger";
|
||||
import { InboundGroupSessionData } from "../../@types/partials";
|
||||
import { IEncryptedPayload } from "../aes";
|
||||
|
||||
/**
|
||||
* Internal module. Defintions for storage for the crypto module
|
||||
* Internal module. Definitions for storage for the crypto module
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -9,9 +37,141 @@
|
||||
*
|
||||
* @interface CryptoStore
|
||||
*/
|
||||
export interface CryptoStore {
|
||||
startup(): Promise<CryptoStore>;
|
||||
deleteAllData(): Promise<void>;
|
||||
getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise<OutgoingRoomKeyRequest>;
|
||||
getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<OutgoingRoomKeyRequest | null>;
|
||||
getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise<OutgoingRoomKeyRequest | null>;
|
||||
getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise<OutgoingRoomKeyRequest[]>;
|
||||
getOutgoingRoomKeyRequestsByTarget(
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
wantedStates: number[],
|
||||
): Promise<OutgoingRoomKeyRequest[]>;
|
||||
updateOutgoingRoomKeyRequest(
|
||||
requestId: string,
|
||||
expectedState: number,
|
||||
updates: Partial<OutgoingRoomKeyRequest>,
|
||||
): Promise<OutgoingRoomKeyRequest | null>;
|
||||
deleteOutgoingRoomKeyRequest(requestId: string, expectedState: number): Promise<OutgoingRoomKeyRequest | null>;
|
||||
|
||||
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index";
|
||||
import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager";
|
||||
// Olm Account
|
||||
getAccount(txn: unknown, func: (accountPickle: string) => void);
|
||||
storeAccount(txn: unknown, accountPickle: string): void;
|
||||
getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey>) => void): void;
|
||||
getSecretStorePrivateKey<T>(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): void;
|
||||
storeCrossSigningKeys(txn: unknown, keys: Record<string, ICrossSigningKey>): void;
|
||||
storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void;
|
||||
|
||||
// Olm Sessions
|
||||
countEndToEndSessions(txn: unknown, func: (count: number) => void): void;
|
||||
getEndToEndSession(
|
||||
deviceKey: string,
|
||||
sessionId: string,
|
||||
txn: unknown,
|
||||
func: (session: ISessionInfo) => void,
|
||||
): void;
|
||||
getEndToEndSessions(
|
||||
deviceKey: string,
|
||||
txn: unknown,
|
||||
func: (sessions: { [sessionId: string]: ISessionInfo }) => void,
|
||||
): void;
|
||||
getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void;
|
||||
storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void;
|
||||
storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void>;
|
||||
getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null>;
|
||||
filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]>;
|
||||
|
||||
// Inbound Group Sessions
|
||||
getEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
txn: unknown,
|
||||
func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void,
|
||||
): void;
|
||||
getAllEndToEndInboundGroupSessions(
|
||||
txn: unknown,
|
||||
func: (session: ISession | null) => void,
|
||||
): void;
|
||||
addEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: InboundGroupSessionData,
|
||||
txn: unknown,
|
||||
): void;
|
||||
storeEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: InboundGroupSessionData,
|
||||
txn: unknown,
|
||||
): void;
|
||||
storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: IWithheld,
|
||||
txn: unknown,
|
||||
): void;
|
||||
|
||||
// Device Data
|
||||
getEndToEndDeviceData(txn: unknown, func: (deviceData: IDeviceData | null) => void): void;
|
||||
storeEndToEndDeviceData(deviceData: IDeviceData, txn: unknown): void;
|
||||
storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: unknown): void;
|
||||
getEndToEndRooms(txn: unknown, func: (rooms: Record<string, IRoomEncryption>) => void): void;
|
||||
getSessionsNeedingBackup(limit: number): Promise<ISession[]>;
|
||||
countSessionsNeedingBackup(txn?: unknown): Promise<number>;
|
||||
unmarkSessionsNeedingBackup(sessions: ISession[], txn?: unknown): Promise<void>;
|
||||
markSessionsNeedingBackup(sessions: ISession[], txn?: unknown): Promise<void>;
|
||||
addSharedHistoryInboundGroupSession(roomId: string, senderKey: string, sessionId: string, txn?: unknown): void;
|
||||
getSharedHistoryInboundGroupSessions(
|
||||
roomId: string,
|
||||
txn?: IDBTransaction,
|
||||
): Promise<[senderKey: string, sessionId: string][]>;
|
||||
|
||||
// Session key backups
|
||||
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: unknown) => T, log?: PrefixedLogger): Promise<T>;
|
||||
}
|
||||
|
||||
export type Mode = "readonly" | "readwrite";
|
||||
|
||||
export interface ISession {
|
||||
senderKey: string;
|
||||
sessionId: string;
|
||||
sessionData?: InboundGroupSessionData;
|
||||
}
|
||||
|
||||
export interface ISessionInfo {
|
||||
deviceKey?: string;
|
||||
sessionId?: string;
|
||||
session?: string;
|
||||
lastReceivedMessageTs?: number;
|
||||
}
|
||||
|
||||
export interface IDeviceData {
|
||||
devices: {
|
||||
[ userId: string ]: {
|
||||
[ deviceId: string ]: IDevice;
|
||||
};
|
||||
};
|
||||
trackingStatus: {
|
||||
[ userId: string ]: TrackingStatus;
|
||||
};
|
||||
crossSigningInfo?: Record<string, ICrossSigningInfo>;
|
||||
syncToken?: string;
|
||||
}
|
||||
|
||||
export interface IProblem {
|
||||
type: string;
|
||||
fixed: boolean;
|
||||
time: number;
|
||||
}
|
||||
|
||||
export interface IWithheld {
|
||||
// eslint-disable-next-line camelcase
|
||||
room_id: string;
|
||||
code: string;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an outgoing room key request
|
||||
|
@@ -1,7 +1,5 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -16,8 +14,24 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { logger } from '../../logger';
|
||||
import { logger, PrefixedLogger } from '../../logger';
|
||||
import * as utils from "../../utils";
|
||||
import {
|
||||
CryptoStore,
|
||||
IDeviceData,
|
||||
IProblem,
|
||||
ISession,
|
||||
ISessionInfo,
|
||||
IWithheld,
|
||||
Mode,
|
||||
OutgoingRoomKeyRequest,
|
||||
} from "./base";
|
||||
import { IRoomKeyRequestBody } from "../index";
|
||||
import { ICrossSigningKey } from "../../client";
|
||||
import { IOlmDevice } from "../algorithms/megolm";
|
||||
import { IRoomEncryption } from "../RoomList";
|
||||
import { InboundGroupSessionData } from "../../@types/partials";
|
||||
import { IEncryptedPayload } from "../aes";
|
||||
|
||||
export const VERSION = 10;
|
||||
const PROFILE_TRANSACTIONS = false;
|
||||
@@ -29,18 +43,17 @@ const PROFILE_TRANSACTIONS = false;
|
||||
*
|
||||
* @implements {module:crypto/store/base~CryptoStore}
|
||||
*/
|
||||
export class Backend {
|
||||
export class Backend implements CryptoStore {
|
||||
private nextTxnId = 0;
|
||||
|
||||
/**
|
||||
* @param {IDBDatabase} db
|
||||
*/
|
||||
constructor(db) {
|
||||
this._db = db;
|
||||
this._nextTxnId = 0;
|
||||
|
||||
constructor(private db: IDBDatabase) {
|
||||
// make sure we close the db on `onversionchange` - otherwise
|
||||
// attempts to delete the database will block (and subsequent
|
||||
// attempts to re-create it will also block).
|
||||
db.onversionchange = (ev) => {
|
||||
db.onversionchange = () => {
|
||||
logger.log(`versionchange for indexeddb ${this._dbName}: closing`);
|
||||
db.close();
|
||||
};
|
||||
@@ -56,11 +69,11 @@ export class Backend {
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}: either the
|
||||
* same instance as passed in, or the existing one.
|
||||
*/
|
||||
getOrAddOutgoingRoomKeyRequest(request) {
|
||||
public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise<OutgoingRoomKeyRequest> {
|
||||
const requestBody = request.requestBody;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite");
|
||||
const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite");
|
||||
txn.onerror = reject;
|
||||
|
||||
// first see if we already have an entry for this request.
|
||||
@@ -99,9 +112,9 @@ export class Backend {
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if
|
||||
* not found
|
||||
*/
|
||||
getOutgoingRoomKeyRequest(requestBody) {
|
||||
public getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<OutgoingRoomKeyRequest | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
|
||||
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
|
||||
txn.onerror = reject;
|
||||
|
||||
this._getOutgoingRoomKeyRequest(txn, requestBody, (existing) => {
|
||||
@@ -122,7 +135,12 @@ export class Backend {
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if
|
||||
* not found.
|
||||
*/
|
||||
_getOutgoingRoomKeyRequest(txn, requestBody, callback) {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
private _getOutgoingRoomKeyRequest(
|
||||
txn: IDBTransaction,
|
||||
requestBody: IRoomKeyRequestBody,
|
||||
callback: (req: OutgoingRoomKeyRequest | null) => void,
|
||||
): void {
|
||||
const store = txn.objectStore("outgoingRoomKeyRequests");
|
||||
|
||||
const idx = store.index("session");
|
||||
@@ -131,8 +149,8 @@ export class Backend {
|
||||
requestBody.session_id,
|
||||
]);
|
||||
|
||||
cursorReq.onsuccess = (ev) => {
|
||||
const cursor = ev.target.result;
|
||||
cursorReq.onsuccess = () => {
|
||||
const cursor = cursorReq.result;
|
||||
if (!cursor) {
|
||||
// no match found
|
||||
callback(null);
|
||||
@@ -162,7 +180,7 @@ export class Backend {
|
||||
* there are no pending requests in those states. If there are multiple
|
||||
* requests in those states, an arbitrary one is chosen.
|
||||
*/
|
||||
getOutgoingRoomKeyRequestByState(wantedStates) {
|
||||
public getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise<OutgoingRoomKeyRequest | null> {
|
||||
if (wantedStates.length === 0) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
@@ -195,7 +213,7 @@ export class Backend {
|
||||
cursorReq.onsuccess = onsuccess;
|
||||
}
|
||||
|
||||
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
|
||||
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
|
||||
const store = txn.objectStore("outgoingRoomKeyRequests");
|
||||
|
||||
const wantedState = wantedStates[stateIndex];
|
||||
@@ -210,19 +228,23 @@ export class Backend {
|
||||
* @param {Number} wantedState
|
||||
* @return {Promise<Array<*>>} All elements in a given state
|
||||
*/
|
||||
getAllOutgoingRoomKeyRequestsByState(wantedState) {
|
||||
public getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise<OutgoingRoomKeyRequest[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
|
||||
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
|
||||
const store = txn.objectStore("outgoingRoomKeyRequests");
|
||||
const index = store.index("state");
|
||||
const request = index.getAll(wantedState);
|
||||
|
||||
request.onsuccess = (ev) => resolve(ev.target.result);
|
||||
request.onerror = (ev) => reject(ev.target.error);
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
|
||||
public getOutgoingRoomKeyRequestsByTarget(
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
wantedStates: number[],
|
||||
): Promise<OutgoingRoomKeyRequest[]> {
|
||||
let stateIndex = 0;
|
||||
const results = [];
|
||||
|
||||
@@ -248,7 +270,7 @@ export class Backend {
|
||||
}
|
||||
}
|
||||
|
||||
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
|
||||
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
|
||||
const store = txn.objectStore("outgoingRoomKeyRequests");
|
||||
|
||||
const wantedState = wantedStates[stateIndex];
|
||||
@@ -270,7 +292,11 @@ export class Backend {
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
|
||||
* updated request, or null if no matching row was found
|
||||
*/
|
||||
updateOutgoingRoomKeyRequest(requestId, expectedState, updates) {
|
||||
public updateOutgoingRoomKeyRequest(
|
||||
requestId: string,
|
||||
expectedState: number,
|
||||
updates: Partial<OutgoingRoomKeyRequest>,
|
||||
): Promise<OutgoingRoomKeyRequest | null> {
|
||||
let result = null;
|
||||
|
||||
function onsuccess(ev) {
|
||||
@@ -291,9 +317,8 @@ export class Backend {
|
||||
result = data;
|
||||
}
|
||||
|
||||
const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite");
|
||||
const cursorReq = txn.objectStore("outgoingRoomKeyRequests")
|
||||
.openCursor(requestId);
|
||||
const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite");
|
||||
const cursorReq = txn.objectStore("outgoingRoomKeyRequests").openCursor(requestId);
|
||||
cursorReq.onsuccess = onsuccess;
|
||||
return promiseifyTxn(txn).then(() => result);
|
||||
}
|
||||
@@ -307,12 +332,14 @@ export class Backend {
|
||||
*
|
||||
* @returns {Promise} resolves once the operation is completed
|
||||
*/
|
||||
deleteOutgoingRoomKeyRequest(requestId, expectedState) {
|
||||
const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite");
|
||||
const cursorReq = txn.objectStore("outgoingRoomKeyRequests")
|
||||
.openCursor(requestId);
|
||||
cursorReq.onsuccess = (ev) => {
|
||||
const cursor = ev.target.result;
|
||||
public deleteOutgoingRoomKeyRequest(
|
||||
requestId: string,
|
||||
expectedState: number,
|
||||
): Promise<OutgoingRoomKeyRequest | null> {
|
||||
const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite");
|
||||
const cursorReq = txn.objectStore("outgoingRoomKeyRequests").openCursor(requestId);
|
||||
cursorReq.onsuccess = () => {
|
||||
const cursor = cursorReq.result;
|
||||
if (!cursor) {
|
||||
return;
|
||||
}
|
||||
@@ -326,12 +353,12 @@ export class Backend {
|
||||
}
|
||||
cursor.delete();
|
||||
};
|
||||
return promiseifyTxn(txn);
|
||||
return promiseifyTxn<OutgoingRoomKeyRequest | null>(txn);
|
||||
}
|
||||
|
||||
// Olm Account
|
||||
|
||||
getAccount(txn, func) {
|
||||
public getAccount(txn: IDBTransaction, func: (accountPickle: string) => void): void {
|
||||
const objectStore = txn.objectStore("account");
|
||||
const getReq = objectStore.get("-");
|
||||
getReq.onsuccess = function() {
|
||||
@@ -343,12 +370,12 @@ export class Backend {
|
||||
};
|
||||
}
|
||||
|
||||
storeAccount(txn, newData) {
|
||||
public storeAccount(txn: IDBTransaction, accountPickle: string): void {
|
||||
const objectStore = txn.objectStore("account");
|
||||
objectStore.put(newData, "-");
|
||||
objectStore.put(accountPickle, "-");
|
||||
}
|
||||
|
||||
getCrossSigningKeys(txn, func) {
|
||||
public getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record<string, ICrossSigningKey>) => void): void {
|
||||
const objectStore = txn.objectStore("account");
|
||||
const getReq = objectStore.get("crossSigningKeys");
|
||||
getReq.onsuccess = function() {
|
||||
@@ -360,7 +387,11 @@ export class Backend {
|
||||
};
|
||||
}
|
||||
|
||||
getSecretStorePrivateKey(txn, func, type) {
|
||||
public getSecretStorePrivateKey<T>(
|
||||
txn: IDBTransaction,
|
||||
func: (key: IEncryptedPayload | null) => T,
|
||||
type: string,
|
||||
): void {
|
||||
const objectStore = txn.objectStore("account");
|
||||
const getReq = objectStore.get(`ssss_cache:${type}`);
|
||||
getReq.onsuccess = function() {
|
||||
@@ -372,19 +403,19 @@ export class Backend {
|
||||
};
|
||||
}
|
||||
|
||||
storeCrossSigningKeys(txn, keys) {
|
||||
public storeCrossSigningKeys(txn: IDBTransaction, keys: Record<string, ICrossSigningKey>): void {
|
||||
const objectStore = txn.objectStore("account");
|
||||
objectStore.put(keys, "crossSigningKeys");
|
||||
}
|
||||
|
||||
storeSecretStorePrivateKey(txn, type, key) {
|
||||
public storeSecretStorePrivateKey(txn: IDBTransaction, type: string, key: IEncryptedPayload): void {
|
||||
const objectStore = txn.objectStore("account");
|
||||
objectStore.put(key, `ssss_cache:${type}`);
|
||||
}
|
||||
|
||||
// Olm Sessions
|
||||
|
||||
countEndToEndSessions(txn, func) {
|
||||
public countEndToEndSessions(txn: IDBTransaction, func: (count: number) => void): void {
|
||||
const objectStore = txn.objectStore("sessions");
|
||||
const countReq = objectStore.count();
|
||||
countReq.onsuccess = function() {
|
||||
@@ -396,7 +427,11 @@ export class Backend {
|
||||
};
|
||||
}
|
||||
|
||||
getEndToEndSessions(deviceKey, txn, func) {
|
||||
public getEndToEndSessions(
|
||||
deviceKey: string,
|
||||
txn: IDBTransaction,
|
||||
func: (sessions: { [sessionId: string]: ISessionInfo }) => void,
|
||||
): void {
|
||||
const objectStore = txn.objectStore("sessions");
|
||||
const idx = objectStore.index("deviceKey");
|
||||
const getReq = idx.openCursor(deviceKey);
|
||||
@@ -419,7 +454,12 @@ export class Backend {
|
||||
};
|
||||
}
|
||||
|
||||
getEndToEndSession(deviceKey, sessionId, txn, func) {
|
||||
public getEndToEndSession(
|
||||
deviceKey: string,
|
||||
sessionId: string,
|
||||
txn: IDBTransaction,
|
||||
func: (sessions: { [ sessionId: string ]: ISessionInfo }) => void,
|
||||
): void {
|
||||
const objectStore = txn.objectStore("sessions");
|
||||
const getReq = objectStore.get([deviceKey, sessionId]);
|
||||
getReq.onsuccess = function() {
|
||||
@@ -438,7 +478,7 @@ export class Backend {
|
||||
};
|
||||
}
|
||||
|
||||
getAllEndToEndSessions(txn, func) {
|
||||
public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo) => void): void {
|
||||
const objectStore = txn.objectStore("sessions");
|
||||
const getReq = objectStore.openCursor();
|
||||
getReq.onsuccess = function() {
|
||||
@@ -456,7 +496,12 @@ export class Backend {
|
||||
};
|
||||
}
|
||||
|
||||
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
|
||||
public storeEndToEndSession(
|
||||
deviceKey: string,
|
||||
sessionId: string,
|
||||
sessionInfo: ISessionInfo,
|
||||
txn: IDBTransaction,
|
||||
): void {
|
||||
const objectStore = txn.objectStore("sessions");
|
||||
objectStore.put({
|
||||
deviceKey,
|
||||
@@ -466,8 +511,8 @@ export class Backend {
|
||||
});
|
||||
}
|
||||
|
||||
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
|
||||
const txn = this._db.transaction("session_problems", "readwrite");
|
||||
public async storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
|
||||
const txn = this.db.transaction("session_problems", "readwrite");
|
||||
const objectStore = txn.objectStore("session_problems");
|
||||
objectStore.put({
|
||||
deviceKey,
|
||||
@@ -478,13 +523,13 @@ export class Backend {
|
||||
return promiseifyTxn(txn);
|
||||
}
|
||||
|
||||
async getEndToEndSessionProblem(deviceKey, timestamp) {
|
||||
public async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null> {
|
||||
let result;
|
||||
const txn = this._db.transaction("session_problems", "readwrite");
|
||||
const txn = this.db.transaction("session_problems", "readwrite");
|
||||
const objectStore = txn.objectStore("session_problems");
|
||||
const index = objectStore.index("deviceKey");
|
||||
const req = index.getAll(deviceKey);
|
||||
req.onsuccess = (event) => {
|
||||
req.onsuccess = () => {
|
||||
const problems = req.result;
|
||||
if (!problems.length) {
|
||||
result = null;
|
||||
@@ -511,14 +556,14 @@ export class Backend {
|
||||
}
|
||||
|
||||
// FIXME: we should probably prune this when devices get deleted
|
||||
async filterOutNotifiedErrorDevices(devices) {
|
||||
const txn = this._db.transaction("notified_error_devices", "readwrite");
|
||||
public async filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]> {
|
||||
const txn = this.db.transaction("notified_error_devices", "readwrite");
|
||||
const objectStore = txn.objectStore("notified_error_devices");
|
||||
|
||||
const ret = [];
|
||||
const ret: IOlmDevice[] = [];
|
||||
|
||||
await Promise.all(devices.map((device) => {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
const { userId, deviceInfo } = device;
|
||||
const getReq = objectStore.get([userId, deviceInfo.deviceId]);
|
||||
getReq.onsuccess = function() {
|
||||
@@ -536,9 +581,14 @@ export class Backend {
|
||||
|
||||
// Inbound group sessions
|
||||
|
||||
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
|
||||
let session = false;
|
||||
let withheld = false;
|
||||
public getEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
txn: IDBTransaction,
|
||||
func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void,
|
||||
): void {
|
||||
let session: InboundGroupSessionData | boolean = false;
|
||||
let withheld: IWithheld | boolean = false;
|
||||
const objectStore = txn.objectStore("inbound_group_sessions");
|
||||
const getReq = objectStore.get([senderCurve25519Key, sessionId]);
|
||||
getReq.onsuccess = function() {
|
||||
@@ -549,7 +599,7 @@ export class Backend {
|
||||
session = null;
|
||||
}
|
||||
if (withheld !== false) {
|
||||
func(session, withheld);
|
||||
func(session as InboundGroupSessionData, withheld as IWithheld);
|
||||
}
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
@@ -566,7 +616,7 @@ export class Backend {
|
||||
withheld = null;
|
||||
}
|
||||
if (session !== false) {
|
||||
func(session, withheld);
|
||||
func(session as InboundGroupSessionData, withheld as IWithheld);
|
||||
}
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
@@ -574,7 +624,7 @@ export class Backend {
|
||||
};
|
||||
}
|
||||
|
||||
getAllEndToEndInboundGroupSessions(txn, func) {
|
||||
public getAllEndToEndInboundGroupSessions(txn: IDBTransaction, func: (session: ISession | null) => void): void {
|
||||
const objectStore = txn.objectStore("inbound_group_sessions");
|
||||
const getReq = objectStore.openCursor();
|
||||
getReq.onsuccess = function() {
|
||||
@@ -600,7 +650,12 @@ export class Backend {
|
||||
};
|
||||
}
|
||||
|
||||
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
|
||||
public addEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: InboundGroupSessionData,
|
||||
txn: IDBTransaction,
|
||||
): void {
|
||||
const objectStore = txn.objectStore("inbound_group_sessions");
|
||||
const addReq = objectStore.add({
|
||||
senderCurve25519Key, sessionId, session: sessionData,
|
||||
@@ -623,23 +678,31 @@ export class Backend {
|
||||
};
|
||||
}
|
||||
|
||||
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
|
||||
public storeEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: InboundGroupSessionData,
|
||||
txn: IDBTransaction,
|
||||
): void {
|
||||
const objectStore = txn.objectStore("inbound_group_sessions");
|
||||
objectStore.put({
|
||||
senderCurve25519Key, sessionId, session: sessionData,
|
||||
});
|
||||
}
|
||||
|
||||
storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key, sessionId, sessionData, txn,
|
||||
) {
|
||||
public storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: IWithheld,
|
||||
txn: IDBTransaction,
|
||||
): void {
|
||||
const objectStore = txn.objectStore("inbound_group_sessions_withheld");
|
||||
objectStore.put({
|
||||
senderCurve25519Key, sessionId, session: sessionData,
|
||||
});
|
||||
}
|
||||
|
||||
getEndToEndDeviceData(txn, func) {
|
||||
public getEndToEndDeviceData(txn: IDBTransaction, func: (deviceData: IDeviceData | null) => void): void {
|
||||
const objectStore = txn.objectStore("device_data");
|
||||
const getReq = objectStore.get("-");
|
||||
getReq.onsuccess = function() {
|
||||
@@ -651,24 +714,24 @@ export class Backend {
|
||||
};
|
||||
}
|
||||
|
||||
storeEndToEndDeviceData(deviceData, txn) {
|
||||
public storeEndToEndDeviceData(deviceData: IDeviceData, txn: IDBTransaction): void {
|
||||
const objectStore = txn.objectStore("device_data");
|
||||
objectStore.put(deviceData, "-");
|
||||
}
|
||||
|
||||
storeEndToEndRoom(roomId, roomInfo, txn) {
|
||||
public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: IDBTransaction): void {
|
||||
const objectStore = txn.objectStore("rooms");
|
||||
objectStore.put(roomInfo, roomId);
|
||||
}
|
||||
|
||||
getEndToEndRooms(txn, func) {
|
||||
public getEndToEndRooms(txn: IDBTransaction, func: (rooms: Record<string, IRoomEncryption>) => void): void {
|
||||
const rooms = {};
|
||||
const objectStore = txn.objectStore("rooms");
|
||||
const getReq = objectStore.openCursor();
|
||||
getReq.onsuccess = function() {
|
||||
const cursor = getReq.result;
|
||||
if (cursor) {
|
||||
rooms[cursor.key] = cursor.value;
|
||||
rooms[cursor.key as string] = cursor.value;
|
||||
cursor.continue();
|
||||
} else {
|
||||
try {
|
||||
@@ -682,11 +745,11 @@ export class Backend {
|
||||
|
||||
// session backups
|
||||
|
||||
getSessionsNeedingBackup(limit) {
|
||||
public getSessionsNeedingBackup(limit: number): Promise<ISession[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sessions = [];
|
||||
|
||||
const txn = this._db.transaction(
|
||||
const txn = this.db.transaction(
|
||||
["sessions_needing_backup", "inbound_group_sessions"],
|
||||
"readonly",
|
||||
);
|
||||
@@ -716,9 +779,9 @@ export class Backend {
|
||||
});
|
||||
}
|
||||
|
||||
countSessionsNeedingBackup(txn) {
|
||||
public countSessionsNeedingBackup(txn?: IDBTransaction): Promise<number> {
|
||||
if (!txn) {
|
||||
txn = this._db.transaction("sessions_needing_backup", "readonly");
|
||||
txn = this.db.transaction("sessions_needing_backup", "readonly");
|
||||
}
|
||||
const objectStore = txn.objectStore("sessions_needing_backup");
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -728,12 +791,12 @@ export class Backend {
|
||||
});
|
||||
}
|
||||
|
||||
unmarkSessionsNeedingBackup(sessions, txn) {
|
||||
public async unmarkSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void> {
|
||||
if (!txn) {
|
||||
txn = this._db.transaction("sessions_needing_backup", "readwrite");
|
||||
txn = this.db.transaction("sessions_needing_backup", "readwrite");
|
||||
}
|
||||
const objectStore = txn.objectStore("sessions_needing_backup");
|
||||
return Promise.all(sessions.map((session) => {
|
||||
await Promise.all(sessions.map((session) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = objectStore.delete([session.senderKey, session.sessionId]);
|
||||
req.onsuccess = resolve;
|
||||
@@ -742,12 +805,12 @@ export class Backend {
|
||||
}));
|
||||
}
|
||||
|
||||
markSessionsNeedingBackup(sessions, txn) {
|
||||
public async markSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void> {
|
||||
if (!txn) {
|
||||
txn = this._db.transaction("sessions_needing_backup", "readwrite");
|
||||
txn = this.db.transaction("sessions_needing_backup", "readwrite");
|
||||
}
|
||||
const objectStore = txn.objectStore("sessions_needing_backup");
|
||||
return Promise.all(sessions.map((session) => {
|
||||
await Promise.all(sessions.map((session) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = objectStore.put({
|
||||
senderCurve25519Key: session.senderKey,
|
||||
@@ -759,9 +822,14 @@ export class Backend {
|
||||
}));
|
||||
}
|
||||
|
||||
addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn) {
|
||||
public addSharedHistoryInboundGroupSession(
|
||||
roomId: string,
|
||||
senderKey: string,
|
||||
sessionId: string,
|
||||
txn?: IDBTransaction,
|
||||
): void {
|
||||
if (!txn) {
|
||||
txn = this._db.transaction(
|
||||
txn = this.db.transaction(
|
||||
"shared_history_inbound_group_sessions", "readwrite",
|
||||
);
|
||||
}
|
||||
@@ -774,9 +842,12 @@ export class Backend {
|
||||
};
|
||||
}
|
||||
|
||||
getSharedHistoryInboundGroupSessions(roomId, txn) {
|
||||
public getSharedHistoryInboundGroupSessions(
|
||||
roomId: string,
|
||||
txn?: IDBTransaction,
|
||||
): Promise<[senderKey: string, sessionId: string][]> {
|
||||
if (!txn) {
|
||||
txn = this._db.transaction(
|
||||
txn = this.db.transaction(
|
||||
"shared_history_inbound_group_sessions", "readonly",
|
||||
);
|
||||
}
|
||||
@@ -791,16 +862,21 @@ export class Backend {
|
||||
});
|
||||
}
|
||||
|
||||
doTxn(mode, stores, func, log = logger) {
|
||||
public doTxn<T>(
|
||||
mode: Mode,
|
||||
stores: Iterable<string>,
|
||||
func: (txn: IDBTransaction) => T,
|
||||
log: PrefixedLogger = logger,
|
||||
): Promise<T> {
|
||||
let startTime;
|
||||
let description;
|
||||
if (PROFILE_TRANSACTIONS) {
|
||||
const txnId = this._nextTxnId++;
|
||||
const txnId = this.nextTxnId++;
|
||||
startTime = Date.now();
|
||||
description = `${mode} crypto store transaction ${txnId} in ${stores}`;
|
||||
log.debug(`Starting ${description}`);
|
||||
}
|
||||
const txn = this._db.transaction(stores, mode);
|
||||
const txn = this.db.transaction(stores, mode);
|
||||
const promise = promiseifyTxn(txn);
|
||||
const result = func(txn);
|
||||
if (PROFILE_TRANSACTIONS) {
|
||||
@@ -818,7 +894,7 @@ export class Backend {
|
||||
}
|
||||
}
|
||||
|
||||
export function upgradeDatabase(db, oldVersion) {
|
||||
export function upgradeDatabase(db: IDBDatabase, oldVersion: number): void {
|
||||
logger.log(
|
||||
`Upgrading IndexedDBCryptoStore from version ${oldVersion}`
|
||||
+ ` to ${VERSION}`,
|
||||
@@ -874,7 +950,7 @@ export function upgradeDatabase(db, oldVersion) {
|
||||
// Expand as needed.
|
||||
}
|
||||
|
||||
function createDatabase(db) {
|
||||
function createDatabase(db: IDBDatabase): void {
|
||||
const outgoingRoomKeyRequestsStore =
|
||||
db.createObjectStore("outgoingRoomKeyRequests", { keyPath: "requestId" });
|
||||
|
||||
@@ -887,15 +963,19 @@ function createDatabase(db) {
|
||||
outgoingRoomKeyRequestsStore.createIndex("state", "state");
|
||||
}
|
||||
|
||||
interface IWrappedIDBTransaction extends IDBTransaction {
|
||||
_mx_abortexception: Error; // eslint-disable-line camelcase
|
||||
}
|
||||
|
||||
/*
|
||||
* Aborts a transaction with a given exception
|
||||
* The transaction promise will be rejected with this exception.
|
||||
*/
|
||||
function abortWithException(txn, e) {
|
||||
function abortWithException(txn: IDBTransaction, e: Error) {
|
||||
// We cheekily stick our exception onto the transaction object here
|
||||
// We could alternatively make the thing we pass back to the app
|
||||
// an object containing the transaction and exception.
|
||||
txn._mx_abortexception = e;
|
||||
(txn as IWrappedIDBTransaction)._mx_abortexception = e;
|
||||
try {
|
||||
txn.abort();
|
||||
} catch (e) {
|
||||
@@ -904,28 +984,28 @@ function abortWithException(txn, e) {
|
||||
}
|
||||
}
|
||||
|
||||
function promiseifyTxn(txn) {
|
||||
function promiseifyTxn<T>(txn: IDBTransaction): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
txn.oncomplete = () => {
|
||||
if (txn._mx_abortexception !== undefined) {
|
||||
reject(txn._mx_abortexception);
|
||||
if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) {
|
||||
reject((txn as IWrappedIDBTransaction)._mx_abortexception);
|
||||
}
|
||||
resolve();
|
||||
resolve(null);
|
||||
};
|
||||
txn.onerror = (event) => {
|
||||
if (txn._mx_abortexception !== undefined) {
|
||||
reject(txn._mx_abortexception);
|
||||
if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) {
|
||||
reject((txn as IWrappedIDBTransaction)._mx_abortexception);
|
||||
} else {
|
||||
logger.log("Error performing indexeddb txn", event);
|
||||
reject(event.target.error);
|
||||
reject(txn.error);
|
||||
}
|
||||
};
|
||||
txn.onabort = (event) => {
|
||||
if (txn._mx_abortexception !== undefined) {
|
||||
reject(txn._mx_abortexception);
|
||||
if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) {
|
||||
reject((txn as IWrappedIDBTransaction)._mx_abortexception);
|
||||
} else {
|
||||
logger.log("Error performing indexeddb txn", event);
|
||||
reject(event.target.error);
|
||||
reject(txn.error);
|
||||
}
|
||||
};
|
||||
});
|
@@ -1,7 +1,5 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -16,12 +14,28 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { logger } from '../../logger';
|
||||
import { logger, PrefixedLogger } from '../../logger';
|
||||
import { LocalStorageCryptoStore } from './localStorage-crypto-store';
|
||||
import { MemoryCryptoStore } from './memory-crypto-store';
|
||||
import * as IndexedDBCryptoStoreBackend from './indexeddb-crypto-store-backend';
|
||||
import { InvalidCryptoStoreError } from '../../errors';
|
||||
import * as IndexedDBHelpers from "../../indexeddb-helpers";
|
||||
import {
|
||||
CryptoStore,
|
||||
IDeviceData,
|
||||
IProblem,
|
||||
ISession,
|
||||
ISessionInfo,
|
||||
IWithheld,
|
||||
Mode,
|
||||
OutgoingRoomKeyRequest,
|
||||
} from "./base";
|
||||
import { IRoomKeyRequestBody } from "../index";
|
||||
import { ICrossSigningKey } from "../../client";
|
||||
import { IOlmDevice } from "../algorithms/megolm";
|
||||
import { IRoomEncryption } from "../RoomList";
|
||||
import { InboundGroupSessionData } from "../../@types/partials";
|
||||
import { IEncryptedPayload } from "../aes";
|
||||
|
||||
/**
|
||||
* Internal module. indexeddb storage for e2e.
|
||||
@@ -35,23 +49,30 @@ import * as IndexedDBHelpers from "../../indexeddb-helpers";
|
||||
*
|
||||
* @implements {module:crypto/store/base~CryptoStore}
|
||||
*/
|
||||
export class IndexedDBCryptoStore {
|
||||
export class IndexedDBCryptoStore implements CryptoStore {
|
||||
public static STORE_ACCOUNT = 'account';
|
||||
public static STORE_SESSIONS = 'sessions';
|
||||
public static STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions';
|
||||
public static STORE_INBOUND_GROUP_SESSIONS_WITHHELD = 'inbound_group_sessions_withheld';
|
||||
public static STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS = 'shared_history_inbound_group_sessions';
|
||||
public static STORE_DEVICE_DATA = 'device_data';
|
||||
public static STORE_ROOMS = 'rooms';
|
||||
public static STORE_BACKUP = 'sessions_needing_backup';
|
||||
|
||||
public static exists(indexedDB: IDBFactory, dbName: string): Promise<boolean> {
|
||||
return IndexedDBHelpers.exists(indexedDB, dbName);
|
||||
}
|
||||
|
||||
private backendPromise: Promise<CryptoStore> = null;
|
||||
private backend: CryptoStore = null;
|
||||
|
||||
/**
|
||||
* Create a new IndexedDBCryptoStore
|
||||
*
|
||||
* @param {IDBFactory} indexedDB global indexedDB instance
|
||||
* @param {string} dbName name of db to connect to
|
||||
*/
|
||||
constructor(indexedDB, dbName) {
|
||||
this._indexedDB = indexedDB;
|
||||
this._dbName = dbName;
|
||||
this._backendPromise = null;
|
||||
this._backend = null;
|
||||
}
|
||||
|
||||
static exists(indexedDB, dbName) {
|
||||
return IndexedDBHelpers.exists(indexedDB, dbName);
|
||||
}
|
||||
constructor(private readonly indexedDB: IDBFactory, private readonly dbName: string) {}
|
||||
|
||||
/**
|
||||
* Ensure the database exists and is up-to-date, or fall back to
|
||||
@@ -62,25 +83,23 @@ export class IndexedDBCryptoStore {
|
||||
* @return {Promise} resolves to either an IndexedDBCryptoStoreBackend.Backend,
|
||||
* or a MemoryCryptoStore
|
||||
*/
|
||||
startup() {
|
||||
if (this._backendPromise) {
|
||||
return this._backendPromise;
|
||||
public startup(): Promise<CryptoStore> {
|
||||
if (this.backendPromise) {
|
||||
return this.backendPromise;
|
||||
}
|
||||
|
||||
this._backendPromise = new Promise((resolve, reject) => {
|
||||
if (!this._indexedDB) {
|
||||
this.backendPromise = new Promise<CryptoStore>((resolve, reject) => {
|
||||
if (!this.indexedDB) {
|
||||
reject(new Error('no indexeddb support available'));
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log(`connecting to indexeddb ${this._dbName}`);
|
||||
logger.log(`connecting to indexeddb ${this.dbName}`);
|
||||
|
||||
const req = this._indexedDB.open(
|
||||
this._dbName, IndexedDBCryptoStoreBackend.VERSION,
|
||||
);
|
||||
const req = this.indexedDB.open(this.dbName, IndexedDBCryptoStoreBackend.VERSION);
|
||||
|
||||
req.onupgradeneeded = (ev) => {
|
||||
const db = ev.target.result;
|
||||
const db = req.result;
|
||||
const oldVersion = ev.oldVersion;
|
||||
IndexedDBCryptoStoreBackend.upgradeDatabase(db, oldVersion);
|
||||
};
|
||||
@@ -93,13 +112,13 @@ export class IndexedDBCryptoStore {
|
||||
|
||||
req.onerror = (ev) => {
|
||||
logger.log("Error connecting to indexeddb", ev);
|
||||
reject(ev.target.error);
|
||||
reject(req.error);
|
||||
};
|
||||
|
||||
req.onsuccess = (r) => {
|
||||
const db = r.target.result;
|
||||
req.onsuccess = () => {
|
||||
const db = req.result;
|
||||
|
||||
logger.log(`connected to indexeddb ${this._dbName}`);
|
||||
logger.log(`connected to indexeddb ${this.dbName}`);
|
||||
resolve(new IndexedDBCryptoStoreBackend.Backend(db));
|
||||
};
|
||||
}).then((backend) => {
|
||||
@@ -114,9 +133,7 @@ export class IndexedDBCryptoStore {
|
||||
],
|
||||
(txn) => {
|
||||
backend.getEndToEndInboundGroupSession('', '', txn, () => {});
|
||||
}).then(() => {
|
||||
return backend;
|
||||
},
|
||||
}).then(() => backend,
|
||||
);
|
||||
}).catch((e) => {
|
||||
if (e.name === 'VersionError') {
|
||||
@@ -126,7 +143,7 @@ export class IndexedDBCryptoStore {
|
||||
throw new InvalidCryptoStoreError(InvalidCryptoStoreError.TOO_NEW);
|
||||
}
|
||||
logger.warn(
|
||||
`unable to connect to indexeddb ${this._dbName}` +
|
||||
`unable to connect to indexeddb ${this.dbName}` +
|
||||
`: falling back to localStorage store: ${e}`,
|
||||
);
|
||||
|
||||
@@ -139,10 +156,11 @@ export class IndexedDBCryptoStore {
|
||||
return new MemoryCryptoStore();
|
||||
}
|
||||
}).then(backend => {
|
||||
this._backend = backend;
|
||||
this.backend = backend;
|
||||
return backend as CryptoStore;
|
||||
});
|
||||
|
||||
return this._backendPromise;
|
||||
return this.backendPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,15 +168,15 @@ export class IndexedDBCryptoStore {
|
||||
*
|
||||
* @returns {Promise} resolves when the store has been cleared.
|
||||
*/
|
||||
deleteAllData() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this._indexedDB) {
|
||||
public deleteAllData(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!this.indexedDB) {
|
||||
reject(new Error('no indexeddb support available'));
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log(`Removing indexeddb instance: ${this._dbName}`);
|
||||
const req = this._indexedDB.deleteDatabase(this._dbName);
|
||||
logger.log(`Removing indexeddb instance: ${this.dbName}`);
|
||||
const req = this.indexedDB.deleteDatabase(this.dbName);
|
||||
|
||||
req.onblocked = () => {
|
||||
logger.log(
|
||||
@@ -168,11 +186,11 @@ export class IndexedDBCryptoStore {
|
||||
|
||||
req.onerror = (ev) => {
|
||||
logger.log("Error deleting data from indexeddb", ev);
|
||||
reject(ev.target.error);
|
||||
reject(req.error);
|
||||
};
|
||||
|
||||
req.onsuccess = () => {
|
||||
logger.log(`Removed indexeddb instance: ${this._dbName}`);
|
||||
logger.log(`Removed indexeddb instance: ${this.dbName}`);
|
||||
resolve();
|
||||
};
|
||||
}).catch((e) => {
|
||||
@@ -193,8 +211,8 @@ export class IndexedDBCryptoStore {
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}: either the
|
||||
* same instance as passed in, or the existing one.
|
||||
*/
|
||||
getOrAddOutgoingRoomKeyRequest(request) {
|
||||
return this._backend.getOrAddOutgoingRoomKeyRequest(request);
|
||||
public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise<OutgoingRoomKeyRequest> {
|
||||
return this.backend.getOrAddOutgoingRoomKeyRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,8 +225,8 @@ export class IndexedDBCryptoStore {
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if
|
||||
* not found
|
||||
*/
|
||||
getOutgoingRoomKeyRequest(requestBody) {
|
||||
return this._backend.getOutgoingRoomKeyRequest(requestBody);
|
||||
public getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<OutgoingRoomKeyRequest | null> {
|
||||
return this.backend.getOutgoingRoomKeyRequest(requestBody);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,8 +239,8 @@ export class IndexedDBCryptoStore {
|
||||
* there are no pending requests in those states. If there are multiple
|
||||
* requests in those states, an arbitrary one is chosen.
|
||||
*/
|
||||
getOutgoingRoomKeyRequestByState(wantedStates) {
|
||||
return this._backend.getOutgoingRoomKeyRequestByState(wantedStates);
|
||||
public getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise<OutgoingRoomKeyRequest | null> {
|
||||
return this.backend.getOutgoingRoomKeyRequestByState(wantedStates);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,8 +250,8 @@ export class IndexedDBCryptoStore {
|
||||
* @param {Number} wantedState
|
||||
* @return {Promise<Array<*>>} Returns an array of requests in the given state
|
||||
*/
|
||||
getAllOutgoingRoomKeyRequestsByState(wantedState) {
|
||||
return this._backend.getAllOutgoingRoomKeyRequestsByState(wantedState);
|
||||
public getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise<OutgoingRoomKeyRequest[]> {
|
||||
return this.backend.getAllOutgoingRoomKeyRequestsByState(wantedState);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,8 +264,12 @@ export class IndexedDBCryptoStore {
|
||||
* @return {Promise} resolves to a list of all the
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
|
||||
*/
|
||||
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
|
||||
return this._backend.getOutgoingRoomKeyRequestsByTarget(
|
||||
public getOutgoingRoomKeyRequestsByTarget(
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
wantedStates: number[],
|
||||
): Promise<OutgoingRoomKeyRequest[]> {
|
||||
return this.backend.getOutgoingRoomKeyRequestsByTarget(
|
||||
userId, deviceId, wantedStates,
|
||||
);
|
||||
}
|
||||
@@ -264,8 +286,12 @@ export class IndexedDBCryptoStore {
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
|
||||
* updated request, or null if no matching row was found
|
||||
*/
|
||||
updateOutgoingRoomKeyRequest(requestId, expectedState, updates) {
|
||||
return this._backend.updateOutgoingRoomKeyRequest(
|
||||
public updateOutgoingRoomKeyRequest(
|
||||
requestId: string,
|
||||
expectedState: number,
|
||||
updates: Partial<OutgoingRoomKeyRequest>,
|
||||
): Promise<OutgoingRoomKeyRequest | null> {
|
||||
return this.backend.updateOutgoingRoomKeyRequest(
|
||||
requestId, expectedState, updates,
|
||||
);
|
||||
}
|
||||
@@ -279,8 +305,11 @@ export class IndexedDBCryptoStore {
|
||||
*
|
||||
* @returns {Promise} resolves once the operation is completed
|
||||
*/
|
||||
deleteOutgoingRoomKeyRequest(requestId, expectedState) {
|
||||
return this._backend.deleteOutgoingRoomKeyRequest(requestId, expectedState);
|
||||
public deleteOutgoingRoomKeyRequest(
|
||||
requestId: string,
|
||||
expectedState: number,
|
||||
): Promise<OutgoingRoomKeyRequest | null> {
|
||||
return this.backend.deleteOutgoingRoomKeyRequest(requestId, expectedState);
|
||||
}
|
||||
|
||||
// Olm Account
|
||||
@@ -292,8 +321,8 @@ export class IndexedDBCryptoStore {
|
||||
* @param {*} txn An active transaction. See doTxn().
|
||||
* @param {function(string)} func Called with the account pickle
|
||||
*/
|
||||
getAccount(txn, func) {
|
||||
this._backend.getAccount(txn, func);
|
||||
public getAccount(txn: IDBTransaction, func: (accountPickle: string) => void) {
|
||||
this.backend.getAccount(txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,10 +330,10 @@ export class IndexedDBCryptoStore {
|
||||
* This requires an active transaction. See doTxn().
|
||||
*
|
||||
* @param {*} txn An active transaction. See doTxn().
|
||||
* @param {string} newData The new account pickle to store.
|
||||
* @param {string} accountPickle The new account pickle to store.
|
||||
*/
|
||||
storeAccount(txn, newData) {
|
||||
this._backend.storeAccount(txn, newData);
|
||||
public storeAccount(txn: IDBTransaction, accountPickle: string): void {
|
||||
this.backend.storeAccount(txn, accountPickle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -315,8 +344,8 @@ export class IndexedDBCryptoStore {
|
||||
* @param {function(string)} func Called with the account keys object:
|
||||
* { key_type: base64 encoded seed } where key type = user_signing_key_seed or self_signing_key_seed
|
||||
*/
|
||||
getCrossSigningKeys(txn, func) {
|
||||
this._backend.getCrossSigningKeys(txn, func);
|
||||
public getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record<string, ICrossSigningKey>) => void): void {
|
||||
this.backend.getCrossSigningKeys(txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,8 +353,12 @@ export class IndexedDBCryptoStore {
|
||||
* @param {function(string)} func Called with the private key
|
||||
* @param {string} type A key type
|
||||
*/
|
||||
getSecretStorePrivateKey(txn, func, type) {
|
||||
this._backend.getSecretStorePrivateKey(txn, func, type);
|
||||
public getSecretStorePrivateKey<T>(
|
||||
txn: IDBTransaction,
|
||||
func: (key: IEncryptedPayload | null) => T,
|
||||
type: string,
|
||||
): void {
|
||||
this.backend.getSecretStorePrivateKey(txn, func, type);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,8 +367,8 @@ export class IndexedDBCryptoStore {
|
||||
* @param {*} txn An active transaction. See doTxn().
|
||||
* @param {string} keys keys object as getCrossSigningKeys()
|
||||
*/
|
||||
storeCrossSigningKeys(txn, keys) {
|
||||
this._backend.storeCrossSigningKeys(txn, keys);
|
||||
public storeCrossSigningKeys(txn: IDBTransaction, keys: Record<string, ICrossSigningKey>): void {
|
||||
this.backend.storeCrossSigningKeys(txn, keys);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,8 +378,8 @@ export class IndexedDBCryptoStore {
|
||||
* @param {string} type The type of cross-signing private key to store
|
||||
* @param {string} key keys object as getCrossSigningKeys()
|
||||
*/
|
||||
storeSecretStorePrivateKey(txn, type, key) {
|
||||
this._backend.storeSecretStorePrivateKey(txn, type, key);
|
||||
public storeSecretStorePrivateKey(txn: IDBTransaction, type: string, key: IEncryptedPayload): void {
|
||||
this.backend.storeSecretStorePrivateKey(txn, type, key);
|
||||
}
|
||||
|
||||
// Olm sessions
|
||||
@@ -356,8 +389,8 @@ export class IndexedDBCryptoStore {
|
||||
* @param {*} txn An active transaction. See doTxn().
|
||||
* @param {function(int)} func Called with the count of sessions
|
||||
*/
|
||||
countEndToEndSessions(txn, func) {
|
||||
this._backend.countEndToEndSessions(txn, func);
|
||||
public countEndToEndSessions(txn: IDBTransaction, func: (count: number) => void): void {
|
||||
this.backend.countEndToEndSessions(txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,8 +405,13 @@ export class IndexedDBCryptoStore {
|
||||
* timestamp in milliseconds at which the session last received
|
||||
* a message.
|
||||
*/
|
||||
getEndToEndSession(deviceKey, sessionId, txn, func) {
|
||||
this._backend.getEndToEndSession(deviceKey, sessionId, txn, func);
|
||||
public getEndToEndSession(
|
||||
deviceKey: string,
|
||||
sessionId: string,
|
||||
txn: IDBTransaction,
|
||||
func: (sessions: { [ sessionId: string ]: ISessionInfo }) => void,
|
||||
): void {
|
||||
this.backend.getEndToEndSession(deviceKey, sessionId, txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -387,8 +425,12 @@ export class IndexedDBCryptoStore {
|
||||
* timestamp in milliseconds at which the session last received
|
||||
* a message.
|
||||
*/
|
||||
getEndToEndSessions(deviceKey, txn, func) {
|
||||
this._backend.getEndToEndSessions(deviceKey, txn, func);
|
||||
public getEndToEndSessions(
|
||||
deviceKey: string,
|
||||
txn: IDBTransaction,
|
||||
func: (sessions: { [sessionId: string]: ISessionInfo }) => void,
|
||||
): void {
|
||||
this.backend.getEndToEndSessions(deviceKey, txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -398,8 +440,8 @@ export class IndexedDBCryptoStore {
|
||||
* an object with, deviceKey, lastReceivedMessageTs, sessionId
|
||||
* and session keys.
|
||||
*/
|
||||
getAllEndToEndSessions(txn, func) {
|
||||
this._backend.getAllEndToEndSessions(txn, func);
|
||||
public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo) => void): void {
|
||||
this.backend.getAllEndToEndSessions(txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -409,22 +451,25 @@ export class IndexedDBCryptoStore {
|
||||
* @param {string} sessionInfo Session information object
|
||||
* @param {*} txn An active transaction. See doTxn().
|
||||
*/
|
||||
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
|
||||
this._backend.storeEndToEndSession(
|
||||
deviceKey, sessionId, sessionInfo, txn,
|
||||
);
|
||||
public storeEndToEndSession(
|
||||
deviceKey: string,
|
||||
sessionId: string,
|
||||
sessionInfo: ISessionInfo,
|
||||
txn: IDBTransaction,
|
||||
): void {
|
||||
this.backend.storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn);
|
||||
}
|
||||
|
||||
storeEndToEndSessionProblem(deviceKey, type, fixed) {
|
||||
return this._backend.storeEndToEndSessionProblem(deviceKey, type, fixed);
|
||||
public storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
|
||||
return this.backend.storeEndToEndSessionProblem(deviceKey, type, fixed);
|
||||
}
|
||||
|
||||
getEndToEndSessionProblem(deviceKey, timestamp) {
|
||||
return this._backend.getEndToEndSessionProblem(deviceKey, timestamp);
|
||||
public getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null> {
|
||||
return this.backend.getEndToEndSessionProblem(deviceKey, timestamp);
|
||||
}
|
||||
|
||||
filterOutNotifiedErrorDevices(devices) {
|
||||
return this._backend.filterOutNotifiedErrorDevices(devices);
|
||||
public filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]> {
|
||||
return this.backend.filterOutNotifiedErrorDevices(devices);
|
||||
}
|
||||
|
||||
// Inbound group sessions
|
||||
@@ -438,10 +483,13 @@ export class IndexedDBCryptoStore {
|
||||
* @param {function(object)} func Called with A map from sessionId
|
||||
* to Base64 end-to-end session.
|
||||
*/
|
||||
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
|
||||
this._backend.getEndToEndInboundGroupSession(
|
||||
senderCurve25519Key, sessionId, txn, func,
|
||||
);
|
||||
public getEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
txn: IDBTransaction,
|
||||
func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void,
|
||||
): void {
|
||||
this.backend.getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,8 +499,11 @@ export class IndexedDBCryptoStore {
|
||||
* in the store with an object having keys {senderKey, sessionId,
|
||||
* sessionData}, then once with null to indicate the end of the list.
|
||||
*/
|
||||
getAllEndToEndInboundGroupSessions(txn, func) {
|
||||
this._backend.getAllEndToEndInboundGroupSessions(txn, func);
|
||||
public getAllEndToEndInboundGroupSessions(
|
||||
txn: IDBTransaction,
|
||||
func: (session: ISession | null) => void,
|
||||
): void {
|
||||
this.backend.getAllEndToEndInboundGroupSessions(txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -464,10 +515,13 @@ export class IndexedDBCryptoStore {
|
||||
* @param {object} sessionData The session data structure
|
||||
* @param {*} txn An active transaction. See doTxn().
|
||||
*/
|
||||
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
|
||||
this._backend.addEndToEndInboundGroupSession(
|
||||
senderCurve25519Key, sessionId, sessionData, txn,
|
||||
);
|
||||
public addEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: InboundGroupSessionData,
|
||||
txn: IDBTransaction,
|
||||
): void {
|
||||
this.backend.addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -479,18 +533,22 @@ export class IndexedDBCryptoStore {
|
||||
* @param {object} sessionData The session data structure
|
||||
* @param {*} txn An active transaction. See doTxn().
|
||||
*/
|
||||
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
|
||||
this._backend.storeEndToEndInboundGroupSession(
|
||||
senderCurve25519Key, sessionId, sessionData, txn,
|
||||
);
|
||||
public storeEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: InboundGroupSessionData,
|
||||
txn: IDBTransaction,
|
||||
): void {
|
||||
this.backend.storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
|
||||
}
|
||||
|
||||
storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key, sessionId, sessionData, txn,
|
||||
) {
|
||||
this._backend.storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key, sessionId, sessionData, txn,
|
||||
);
|
||||
public storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: IWithheld,
|
||||
txn: IDBTransaction,
|
||||
): void {
|
||||
this.backend.storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn);
|
||||
}
|
||||
|
||||
// End-to-end device tracking
|
||||
@@ -505,8 +563,8 @@ export class IndexedDBCryptoStore {
|
||||
* @param {Object} deviceData
|
||||
* @param {*} txn An active transaction. See doTxn().
|
||||
*/
|
||||
storeEndToEndDeviceData(deviceData, txn) {
|
||||
this._backend.storeEndToEndDeviceData(deviceData, txn);
|
||||
public storeEndToEndDeviceData(deviceData: IDeviceData, txn: IDBTransaction): void {
|
||||
this.backend.storeEndToEndDeviceData(deviceData, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -516,8 +574,8 @@ export class IndexedDBCryptoStore {
|
||||
* @param {function(Object)} func Function called with the
|
||||
* device data
|
||||
*/
|
||||
getEndToEndDeviceData(txn, func) {
|
||||
this._backend.getEndToEndDeviceData(txn, func);
|
||||
public getEndToEndDeviceData(txn: IDBTransaction, func: (deviceData: IDeviceData | null) => void): void {
|
||||
this.backend.getEndToEndDeviceData(txn, func);
|
||||
}
|
||||
|
||||
// End to End Rooms
|
||||
@@ -528,8 +586,8 @@ export class IndexedDBCryptoStore {
|
||||
* @param {object} roomInfo The end-to-end info for the room.
|
||||
* @param {*} txn An active transaction. See doTxn().
|
||||
*/
|
||||
storeEndToEndRoom(roomId, roomInfo, txn) {
|
||||
this._backend.storeEndToEndRoom(roomId, roomInfo, txn);
|
||||
public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: IDBTransaction): void {
|
||||
this.backend.storeEndToEndRoom(roomId, roomInfo, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -537,20 +595,20 @@ export class IndexedDBCryptoStore {
|
||||
* @param {*} txn An active transaction. See doTxn().
|
||||
* @param {function(Object)} func Function called with the end to end encrypted rooms
|
||||
*/
|
||||
getEndToEndRooms(txn, func) {
|
||||
this._backend.getEndToEndRooms(txn, func);
|
||||
public getEndToEndRooms(txn: IDBTransaction, func: (rooms: Record<string, IRoomEncryption>) => void): void {
|
||||
this.backend.getEndToEndRooms(txn, func);
|
||||
}
|
||||
|
||||
// session backups
|
||||
|
||||
/**
|
||||
* Get the inbound group sessions that need to be backed up.
|
||||
* @param {integer} limit The maximum number of sessions to retrieve. 0
|
||||
* @param {number} limit The maximum number of sessions to retrieve. 0
|
||||
* for no limit.
|
||||
* @returns {Promise} resolves to an array of inbound group sessions
|
||||
*/
|
||||
getSessionsNeedingBackup(limit) {
|
||||
return this._backend.getSessionsNeedingBackup(limit);
|
||||
public getSessionsNeedingBackup(limit: number): Promise<ISession[]> {
|
||||
return this.backend.getSessionsNeedingBackup(limit);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -558,8 +616,8 @@ export class IndexedDBCryptoStore {
|
||||
* @param {*} txn An active transaction. See doTxn(). (optional)
|
||||
* @returns {Promise} resolves to the number of sessions
|
||||
*/
|
||||
countSessionsNeedingBackup(txn) {
|
||||
return this._backend.countSessionsNeedingBackup(txn);
|
||||
public countSessionsNeedingBackup(txn?: IDBTransaction): Promise<number> {
|
||||
return this.backend.countSessionsNeedingBackup(txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -568,8 +626,8 @@ export class IndexedDBCryptoStore {
|
||||
* @param {*} txn An active transaction. See doTxn(). (optional)
|
||||
* @returns {Promise} resolves when the sessions are unmarked
|
||||
*/
|
||||
unmarkSessionsNeedingBackup(sessions, txn) {
|
||||
return this._backend.unmarkSessionsNeedingBackup(sessions, txn);
|
||||
public unmarkSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void> {
|
||||
return this.backend.unmarkSessionsNeedingBackup(sessions, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -578,8 +636,8 @@ export class IndexedDBCryptoStore {
|
||||
* @param {*} txn An active transaction. See doTxn(). (optional)
|
||||
* @returns {Promise} resolves when the sessions are marked
|
||||
*/
|
||||
markSessionsNeedingBackup(sessions, txn) {
|
||||
return this._backend.markSessionsNeedingBackup(sessions, txn);
|
||||
public markSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void> {
|
||||
return this.backend.markSessionsNeedingBackup(sessions, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -589,10 +647,13 @@ export class IndexedDBCryptoStore {
|
||||
* @param {string} sessionId The ID of the session
|
||||
* @param {*} txn An active transaction. See doTxn(). (optional)
|
||||
*/
|
||||
addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn) {
|
||||
this._backend.addSharedHistoryInboundGroupSession(
|
||||
roomId, senderKey, sessionId, txn,
|
||||
);
|
||||
public addSharedHistoryInboundGroupSession(
|
||||
roomId: string,
|
||||
senderKey: string,
|
||||
sessionId: string,
|
||||
txn?: IDBTransaction,
|
||||
): void {
|
||||
this.backend.addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -601,8 +662,11 @@ export class IndexedDBCryptoStore {
|
||||
* @param {*} txn An active transaction. See doTxn(). (optional)
|
||||
* @returns {Promise} Resolves to an array of [senderKey, sessionId]
|
||||
*/
|
||||
getSharedHistoryInboundGroupSessions(roomId, txn) {
|
||||
return this._backend.getSharedHistoryInboundGroupSessions(roomId, txn);
|
||||
public getSharedHistoryInboundGroupSessions(
|
||||
roomId: string,
|
||||
txn?: IDBTransaction,
|
||||
): Promise<[senderKey: string, sessionId: string][]> {
|
||||
return this.backend.getSharedHistoryInboundGroupSessions(roomId, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -627,18 +691,7 @@ export class IndexedDBCryptoStore {
|
||||
* reject with that exception. On synchronous backends, the
|
||||
* exception will propagate to the caller of the getFoo method.
|
||||
*/
|
||||
doTxn(mode, stores, func, log) {
|
||||
return this._backend.doTxn(mode, stores, func, log);
|
||||
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: IDBTransaction) => T, log?: PrefixedLogger): Promise<T> {
|
||||
return this.backend.doTxn(mode, stores, func, log);
|
||||
}
|
||||
}
|
||||
|
||||
IndexedDBCryptoStore.STORE_ACCOUNT = 'account';
|
||||
IndexedDBCryptoStore.STORE_SESSIONS = 'sessions';
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions';
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD
|
||||
= 'inbound_group_sessions_withheld';
|
||||
IndexedDBCryptoStore.STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS
|
||||
= 'shared_history_inbound_group_sessions';
|
||||
IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data';
|
||||
IndexedDBCryptoStore.STORE_ROOMS = 'rooms';
|
||||
IndexedDBCryptoStore.STORE_BACKUP = 'sessions_needing_backup';
|
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
Copyright 2017, 2018 New Vector Ltd
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +16,12 @@ limitations under the License.
|
||||
|
||||
import { logger } from '../../logger';
|
||||
import { MemoryCryptoStore } from './memory-crypto-store';
|
||||
import { IDeviceData, IProblem, ISession, ISessionInfo, IWithheld, Mode } from "./base";
|
||||
import { IOlmDevice } from "../algorithms/megolm";
|
||||
import { IRoomEncryption } from "../RoomList";
|
||||
import { ICrossSigningKey } from "../../client";
|
||||
import { InboundGroupSessionData } from "../../@types/partials";
|
||||
import { IEncryptedPayload } from "../aes";
|
||||
|
||||
/**
|
||||
* Internal module. Partial localStorage backed storage for e2e.
|
||||
@@ -38,23 +43,23 @@ const KEY_INBOUND_SESSION_WITHHELD_PREFIX = E2E_PREFIX + "inboundgroupsessions.w
|
||||
const KEY_ROOMS_PREFIX = E2E_PREFIX + "rooms/";
|
||||
const KEY_SESSIONS_NEEDING_BACKUP = E2E_PREFIX + "sessionsneedingbackup";
|
||||
|
||||
function keyEndToEndSessions(deviceKey) {
|
||||
function keyEndToEndSessions(deviceKey: string): string {
|
||||
return E2E_PREFIX + "sessions/" + deviceKey;
|
||||
}
|
||||
|
||||
function keyEndToEndSessionProblems(deviceKey) {
|
||||
function keyEndToEndSessionProblems(deviceKey: string): string {
|
||||
return E2E_PREFIX + "session.problems/" + deviceKey;
|
||||
}
|
||||
|
||||
function keyEndToEndInboundGroupSession(senderKey, sessionId) {
|
||||
function keyEndToEndInboundGroupSession(senderKey: string, sessionId: string): string {
|
||||
return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId;
|
||||
}
|
||||
|
||||
function keyEndToEndInboundGroupSessionWithheld(senderKey, sessionId) {
|
||||
function keyEndToEndInboundGroupSessionWithheld(senderKey: string, sessionId: string): string {
|
||||
return KEY_INBOUND_SESSION_WITHHELD_PREFIX + senderKey + "/" + sessionId;
|
||||
}
|
||||
|
||||
function keyEndToEndRoomsPrefix(roomId) {
|
||||
function keyEndToEndRoomsPrefix(roomId: string): string {
|
||||
return KEY_ROOMS_PREFIX + roomId;
|
||||
}
|
||||
|
||||
@@ -62,24 +67,23 @@ function keyEndToEndRoomsPrefix(roomId) {
|
||||
* @implements {module:crypto/store/base~CryptoStore}
|
||||
*/
|
||||
export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
constructor(webStore) {
|
||||
super();
|
||||
this.store = webStore;
|
||||
}
|
||||
|
||||
static exists(webStore) {
|
||||
const length = webStore.length;
|
||||
public static exists(store: Storage): boolean {
|
||||
const length = store.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (webStore.key(i).startsWith(E2E_PREFIX)) {
|
||||
if (store.key(i).startsWith(E2E_PREFIX)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
constructor(private readonly store: Storage) {
|
||||
super();
|
||||
}
|
||||
|
||||
// Olm Sessions
|
||||
|
||||
countEndToEndSessions(txn, func) {
|
||||
public countEndToEndSessions(txn: unknown, func: (count: number) => void): void {
|
||||
let count = 0;
|
||||
for (let i = 0; i < this.store.length; ++i) {
|
||||
if (this.store.key(i).startsWith(keyEndToEndSessions(''))) ++count;
|
||||
@@ -87,9 +91,10 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
func(count);
|
||||
}
|
||||
|
||||
_getEndToEndSessions(deviceKey, txn, func) {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
private _getEndToEndSessions(deviceKey: string): Record<string, ISessionInfo> {
|
||||
const sessions = getJsonItem(this.store, keyEndToEndSessions(deviceKey));
|
||||
const fixedSessions = {};
|
||||
const fixedSessions: Record<string, ISessionInfo> = {};
|
||||
|
||||
// fix up any old sessions to be objects rather than just the base64 pickle
|
||||
for (const [sid, val] of Object.entries(sessions || {})) {
|
||||
@@ -105,16 +110,25 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
return fixedSessions;
|
||||
}
|
||||
|
||||
getEndToEndSession(deviceKey, sessionId, txn, func) {
|
||||
public getEndToEndSession(
|
||||
deviceKey: string,
|
||||
sessionId: string,
|
||||
txn: unknown,
|
||||
func: (session: ISessionInfo) => void,
|
||||
): void {
|
||||
const sessions = this._getEndToEndSessions(deviceKey);
|
||||
func(sessions[sessionId] || {});
|
||||
}
|
||||
|
||||
getEndToEndSessions(deviceKey, txn, func) {
|
||||
public getEndToEndSessions(
|
||||
deviceKey: string,
|
||||
txn: unknown,
|
||||
func: (sessions: { [sessionId: string]: ISessionInfo }) => void,
|
||||
): void {
|
||||
func(this._getEndToEndSessions(deviceKey) || {});
|
||||
}
|
||||
|
||||
getAllEndToEndSessions(txn, func) {
|
||||
public getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void {
|
||||
for (let i = 0; i < this.store.length; ++i) {
|
||||
if (this.store.key(i).startsWith(keyEndToEndSessions(''))) {
|
||||
const deviceKey = this.store.key(i).split('/')[1];
|
||||
@@ -125,17 +139,15 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
}
|
||||
}
|
||||
|
||||
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
|
||||
public storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void {
|
||||
const sessions = this._getEndToEndSessions(deviceKey) || {};
|
||||
sessions[sessionId] = sessionInfo;
|
||||
setJsonItem(
|
||||
this.store, keyEndToEndSessions(deviceKey), sessions,
|
||||
);
|
||||
setJsonItem(this.store, keyEndToEndSessions(deviceKey), sessions);
|
||||
}
|
||||
|
||||
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
|
||||
public async storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
|
||||
const key = keyEndToEndSessionProblems(deviceKey);
|
||||
const problems = getJsonItem(this.store, key) || [];
|
||||
const problems = getJsonItem<IProblem[]>(this.store, key) || [];
|
||||
problems.push({ type, fixed, time: Date.now() });
|
||||
problems.sort((a, b) => {
|
||||
return a.time - b.time;
|
||||
@@ -143,9 +155,9 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
setJsonItem(this.store, key, problems);
|
||||
}
|
||||
|
||||
async getEndToEndSessionProblem(deviceKey, timestamp) {
|
||||
async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null> {
|
||||
const key = keyEndToEndSessionProblems(deviceKey);
|
||||
const problems = getJsonItem(this.store, key) || [];
|
||||
const problems = getJsonItem<IProblem[]>(this.store, key) || [];
|
||||
if (!problems.length) {
|
||||
return null;
|
||||
}
|
||||
@@ -162,9 +174,8 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
}
|
||||
}
|
||||
|
||||
async filterOutNotifiedErrorDevices(devices) {
|
||||
const notifiedErrorDevices =
|
||||
getJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES) || {};
|
||||
public async filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]> {
|
||||
const notifiedErrorDevices = getJsonItem<string[]>(this.store, KEY_NOTIFIED_ERROR_DEVICES) || {};
|
||||
const ret = [];
|
||||
|
||||
for (const device of devices) {
|
||||
@@ -187,7 +198,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
|
||||
// Inbound Group Sessions
|
||||
|
||||
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
|
||||
public getEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
txn: unknown,
|
||||
func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void,
|
||||
): void {
|
||||
func(
|
||||
getJsonItem(
|
||||
this.store,
|
||||
@@ -200,7 +216,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
);
|
||||
}
|
||||
|
||||
getAllEndToEndInboundGroupSessions(txn, func) {
|
||||
public getAllEndToEndInboundGroupSessions(txn: unknown, func: (session: ISession | null) => void): void {
|
||||
for (let i = 0; i < this.store.length; ++i) {
|
||||
const key = this.store.key(i);
|
||||
if (key.startsWith(KEY_INBOUND_SESSION_PREFIX)) {
|
||||
@@ -219,7 +235,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
func(null);
|
||||
}
|
||||
|
||||
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
|
||||
public addEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: InboundGroupSessionData,
|
||||
txn: unknown,
|
||||
): void {
|
||||
const existing = getJsonItem(
|
||||
this.store,
|
||||
keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId),
|
||||
@@ -231,7 +252,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
}
|
||||
}
|
||||
|
||||
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
|
||||
public storeEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: InboundGroupSessionData,
|
||||
txn: unknown,
|
||||
): void {
|
||||
setJsonItem(
|
||||
this.store,
|
||||
keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId),
|
||||
@@ -239,9 +265,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
);
|
||||
}
|
||||
|
||||
storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key, sessionId, sessionData, txn,
|
||||
) {
|
||||
public storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: IWithheld,
|
||||
txn: unknown,
|
||||
): void {
|
||||
setJsonItem(
|
||||
this.store,
|
||||
keyEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId),
|
||||
@@ -249,25 +278,19 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
);
|
||||
}
|
||||
|
||||
getEndToEndDeviceData(txn, func) {
|
||||
func(getJsonItem(
|
||||
this.store, KEY_DEVICE_DATA,
|
||||
));
|
||||
public getEndToEndDeviceData(txn: unknown, func: (deviceData: IDeviceData | null) => void): void {
|
||||
func(getJsonItem(this.store, KEY_DEVICE_DATA));
|
||||
}
|
||||
|
||||
storeEndToEndDeviceData(deviceData, txn) {
|
||||
setJsonItem(
|
||||
this.store, KEY_DEVICE_DATA, deviceData,
|
||||
);
|
||||
public storeEndToEndDeviceData(deviceData: IDeviceData, txn: unknown): void {
|
||||
setJsonItem(this.store, KEY_DEVICE_DATA, deviceData);
|
||||
}
|
||||
|
||||
storeEndToEndRoom(roomId, roomInfo, txn) {
|
||||
setJsonItem(
|
||||
this.store, keyEndToEndRoomsPrefix(roomId), roomInfo,
|
||||
);
|
||||
public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: unknown): void {
|
||||
setJsonItem(this.store, keyEndToEndRoomsPrefix(roomId), roomInfo);
|
||||
}
|
||||
|
||||
getEndToEndRooms(txn, func) {
|
||||
public getEndToEndRooms(txn: unknown, func: (rooms: Record<string, IRoomEncryption>) => void): void {
|
||||
const result = {};
|
||||
const prefix = keyEndToEndRoomsPrefix('');
|
||||
|
||||
@@ -281,9 +304,8 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
func(result);
|
||||
}
|
||||
|
||||
getSessionsNeedingBackup(limit) {
|
||||
const sessionsNeedingBackup
|
||||
= getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
|
||||
public getSessionsNeedingBackup(limit: number): Promise<ISession[]> {
|
||||
const sessionsNeedingBackup = getJsonItem<string[]>(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
|
||||
const sessions = [];
|
||||
|
||||
for (const session in sessionsNeedingBackup) {
|
||||
@@ -309,13 +331,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
return Promise.resolve(sessions);
|
||||
}
|
||||
|
||||
countSessionsNeedingBackup() {
|
||||
const sessionsNeedingBackup
|
||||
= getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
|
||||
public countSessionsNeedingBackup(): Promise<number> {
|
||||
const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
|
||||
return Promise.resolve(Object.keys(sessionsNeedingBackup).length);
|
||||
}
|
||||
|
||||
unmarkSessionsNeedingBackup(sessions) {
|
||||
public unmarkSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
|
||||
const sessionsNeedingBackup
|
||||
= getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
|
||||
for (const session of sessions) {
|
||||
@@ -327,7 +348,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
markSessionsNeedingBackup(sessions) {
|
||||
public markSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
|
||||
const sessionsNeedingBackup
|
||||
= getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
|
||||
for (const session of sessions) {
|
||||
@@ -344,52 +365,46 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
*
|
||||
* @returns {Promise} Promise which resolves when the store has been cleared.
|
||||
*/
|
||||
deleteAllData() {
|
||||
public deleteAllData(): Promise<void> {
|
||||
this.store.removeItem(KEY_END_TO_END_ACCOUNT);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Olm account
|
||||
|
||||
getAccount(txn, func) {
|
||||
const account = getJsonItem(this.store, KEY_END_TO_END_ACCOUNT);
|
||||
func(account);
|
||||
public getAccount(txn: unknown, func: (accountPickle: string) => void): void {
|
||||
const accountPickle = getJsonItem<string>(this.store, KEY_END_TO_END_ACCOUNT);
|
||||
func(accountPickle);
|
||||
}
|
||||
|
||||
storeAccount(txn, newData) {
|
||||
setJsonItem(
|
||||
this.store, KEY_END_TO_END_ACCOUNT, newData,
|
||||
);
|
||||
public storeAccount(txn: unknown, accountPickle: string): void {
|
||||
setJsonItem(this.store, KEY_END_TO_END_ACCOUNT, accountPickle);
|
||||
}
|
||||
|
||||
getCrossSigningKeys(txn, func) {
|
||||
const keys = getJsonItem(this.store, KEY_CROSS_SIGNING_KEYS);
|
||||
public getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey>) => void): void {
|
||||
const keys = getJsonItem<Record<string, ICrossSigningKey>>(this.store, KEY_CROSS_SIGNING_KEYS);
|
||||
func(keys);
|
||||
}
|
||||
|
||||
getSecretStorePrivateKey(txn, func, type) {
|
||||
const key = getJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`);
|
||||
public getSecretStorePrivateKey<T>(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): T {
|
||||
const key = getJsonItem<IEncryptedPayload>(this.store, E2E_PREFIX + `ssss_cache.${type}`);
|
||||
func(key);
|
||||
}
|
||||
|
||||
storeCrossSigningKeys(txn, keys) {
|
||||
setJsonItem(
|
||||
this.store, KEY_CROSS_SIGNING_KEYS, keys,
|
||||
);
|
||||
public storeCrossSigningKeys(txn: unknown, keys: Record<string, ICrossSigningKey>): void {
|
||||
setJsonItem(this.store, KEY_CROSS_SIGNING_KEYS, keys);
|
||||
}
|
||||
|
||||
storeSecretStorePrivateKey(txn, type, key) {
|
||||
setJsonItem(
|
||||
this.store, E2E_PREFIX + `ssss_cache.${type}`, key,
|
||||
);
|
||||
public storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void {
|
||||
setJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`, key);
|
||||
}
|
||||
|
||||
doTxn(mode, stores, func) {
|
||||
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: unknown) => T): Promise<T> {
|
||||
return Promise.resolve(func(null));
|
||||
}
|
||||
}
|
||||
|
||||
function getJsonItem(store, key) {
|
||||
function getJsonItem<T>(store: Storage, key: string): T | null {
|
||||
try {
|
||||
// if the key is absent, store.getItem() returns null, and
|
||||
// JSON.parse(null) === null, so this returns null.
|
||||
@@ -401,6 +416,6 @@ function getJsonItem(store, key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function setJsonItem(store, key, val) {
|
||||
function setJsonItem<T>(store: Storage, key: string, val: T): void {
|
||||
store.setItem(key, JSON.stringify(val));
|
||||
}
|
@@ -1,7 +1,5 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +16,22 @@ limitations under the License.
|
||||
|
||||
import { logger } from '../../logger';
|
||||
import * as utils from "../../utils";
|
||||
import {
|
||||
CryptoStore,
|
||||
IDeviceData,
|
||||
IProblem,
|
||||
ISession,
|
||||
ISessionInfo,
|
||||
IWithheld,
|
||||
Mode,
|
||||
OutgoingRoomKeyRequest,
|
||||
} from "./base";
|
||||
import { IRoomKeyRequestBody } from "../index";
|
||||
import { ICrossSigningKey } from "../../client";
|
||||
import { IOlmDevice } from "../algorithms/megolm";
|
||||
import { IRoomEncryption } from "../RoomList";
|
||||
import { InboundGroupSessionData } from "../../@types/partials";
|
||||
import { IEncryptedPayload } from "../aes";
|
||||
|
||||
/**
|
||||
* Internal module. in-memory storage for e2e.
|
||||
@@ -28,32 +42,22 @@ import * as utils from "../../utils";
|
||||
/**
|
||||
* @implements {module:crypto/store/base~CryptoStore}
|
||||
*/
|
||||
export class MemoryCryptoStore {
|
||||
constructor() {
|
||||
this._outgoingRoomKeyRequests = [];
|
||||
this._account = null;
|
||||
this._crossSigningKeys = null;
|
||||
this._privateKeys = {};
|
||||
this._backupKeys = {};
|
||||
export class MemoryCryptoStore implements CryptoStore {
|
||||
private outgoingRoomKeyRequests: OutgoingRoomKeyRequest[] = [];
|
||||
private account: string = null;
|
||||
private crossSigningKeys: Record<string, ICrossSigningKey> = null;
|
||||
private privateKeys: Record<string, IEncryptedPayload> = {};
|
||||
|
||||
// Map of {devicekey -> {sessionId -> session pickle}}
|
||||
this._sessions = {};
|
||||
// Map of {devicekey -> array of problems}
|
||||
this._sessionProblems = {};
|
||||
// Map of {userId -> deviceId -> true}
|
||||
this._notifiedErrorDevices = {};
|
||||
// Map of {senderCurve25519Key+'/'+sessionId -> session data object}
|
||||
this._inboundGroupSessions = {};
|
||||
this._inboundGroupSessionsWithheld = {};
|
||||
private sessions: { [deviceKey: string]: { [sessionId: string]: ISessionInfo } } = {};
|
||||
private sessionProblems: { [deviceKey: string]: IProblem[] } = {};
|
||||
private notifiedErrorDevices: { [userId: string]: { [deviceId: string]: boolean } } = {};
|
||||
private inboundGroupSessions: { [sessionKey: string]: InboundGroupSessionData } = {};
|
||||
private inboundGroupSessionsWithheld: Record<string, IWithheld> = {};
|
||||
// Opaque device data object
|
||||
this._deviceData = null;
|
||||
// roomId -> Opaque roomInfo object
|
||||
this._rooms = {};
|
||||
// Set of {senderCurve25519Key+'/'+sessionId}
|
||||
this._sessionsNeedingBackup = {};
|
||||
// roomId -> array of [senderKey, sessionId]
|
||||
this._sharedHistoryInboundGroupSessions = {};
|
||||
}
|
||||
private deviceData: IDeviceData = null;
|
||||
private rooms: { [roomId: string]: IRoomEncryption } = {};
|
||||
private sessionsNeedingBackup: { [sessionKey: string]: boolean } = {};
|
||||
private sharedHistoryInboundGroupSessions: { [roomId: string]: [senderKey: string, sessionId: string][] } = {};
|
||||
|
||||
/**
|
||||
* Ensure the database exists and is up-to-date.
|
||||
@@ -62,7 +66,7 @@ export class MemoryCryptoStore {
|
||||
*
|
||||
* @return {Promise} resolves to the store.
|
||||
*/
|
||||
async startup() {
|
||||
public async startup(): Promise<CryptoStore> {
|
||||
// No startup work to do for the memory store.
|
||||
return this;
|
||||
}
|
||||
@@ -72,7 +76,7 @@ export class MemoryCryptoStore {
|
||||
*
|
||||
* @returns {Promise} Promise which resolves when the store has been cleared.
|
||||
*/
|
||||
deleteAllData() {
|
||||
public deleteAllData(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -86,7 +90,7 @@ export class MemoryCryptoStore {
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}: either the
|
||||
* same instance as passed in, or the existing one.
|
||||
*/
|
||||
getOrAddOutgoingRoomKeyRequest(request) {
|
||||
public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise<OutgoingRoomKeyRequest> {
|
||||
const requestBody = request.requestBody;
|
||||
|
||||
return utils.promiseTry(() => {
|
||||
@@ -109,7 +113,7 @@ export class MemoryCryptoStore {
|
||||
`enqueueing key request for ${requestBody.room_id} / ` +
|
||||
requestBody.session_id,
|
||||
);
|
||||
this._outgoingRoomKeyRequests.push(request);
|
||||
this.outgoingRoomKeyRequests.push(request);
|
||||
return request;
|
||||
});
|
||||
}
|
||||
@@ -124,7 +128,7 @@ export class MemoryCryptoStore {
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if
|
||||
* not found
|
||||
*/
|
||||
getOutgoingRoomKeyRequest(requestBody) {
|
||||
public getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<OutgoingRoomKeyRequest | null> {
|
||||
return Promise.resolve(this._getOutgoingRoomKeyRequest(requestBody));
|
||||
}
|
||||
|
||||
@@ -139,8 +143,9 @@ export class MemoryCryptoStore {
|
||||
* @return {module:crypto/store/base~OutgoingRoomKeyRequest?}
|
||||
* the matching request, or null if not found
|
||||
*/
|
||||
_getOutgoingRoomKeyRequest(requestBody) {
|
||||
for (const existing of this._outgoingRoomKeyRequests) {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
private _getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): OutgoingRoomKeyRequest | null {
|
||||
for (const existing of this.outgoingRoomKeyRequests) {
|
||||
if (utils.deepCompare(existing.requestBody, requestBody)) {
|
||||
return existing;
|
||||
}
|
||||
@@ -157,8 +162,8 @@ export class MemoryCryptoStore {
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if
|
||||
* there are no pending requests in those states
|
||||
*/
|
||||
getOutgoingRoomKeyRequestByState(wantedStates) {
|
||||
for (const req of this._outgoingRoomKeyRequests) {
|
||||
public getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise<OutgoingRoomKeyRequest | null> {
|
||||
for (const req of this.outgoingRoomKeyRequests) {
|
||||
for (const state of wantedStates) {
|
||||
if (req.state === state) {
|
||||
return Promise.resolve(req);
|
||||
@@ -173,18 +178,22 @@ export class MemoryCryptoStore {
|
||||
* @param {Number} wantedState
|
||||
* @return {Promise<Array<*>>} All OutgoingRoomKeyRequests in state
|
||||
*/
|
||||
getAllOutgoingRoomKeyRequestsByState(wantedState) {
|
||||
public getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise<OutgoingRoomKeyRequest[]> {
|
||||
return Promise.resolve(
|
||||
this._outgoingRoomKeyRequests.filter(
|
||||
this.outgoingRoomKeyRequests.filter(
|
||||
(r) => r.state == wantedState,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
|
||||
public getOutgoingRoomKeyRequestsByTarget(
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
wantedStates: number[],
|
||||
): Promise<OutgoingRoomKeyRequest[]> {
|
||||
const results = [];
|
||||
|
||||
for (const req of this._outgoingRoomKeyRequests) {
|
||||
for (const req of this.outgoingRoomKeyRequests) {
|
||||
for (const state of wantedStates) {
|
||||
if (req.state === state && req.recipients.includes({ userId, deviceId })) {
|
||||
results.push(req);
|
||||
@@ -206,8 +215,12 @@ export class MemoryCryptoStore {
|
||||
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
|
||||
* updated request, or null if no matching row was found
|
||||
*/
|
||||
updateOutgoingRoomKeyRequest(requestId, expectedState, updates) {
|
||||
for (const req of this._outgoingRoomKeyRequests) {
|
||||
public updateOutgoingRoomKeyRequest(
|
||||
requestId: string,
|
||||
expectedState: number,
|
||||
updates: Partial<OutgoingRoomKeyRequest>,
|
||||
): Promise<OutgoingRoomKeyRequest | null> {
|
||||
for (const req of this.outgoingRoomKeyRequests) {
|
||||
if (req.requestId !== requestId) {
|
||||
continue;
|
||||
}
|
||||
@@ -235,9 +248,12 @@ export class MemoryCryptoStore {
|
||||
*
|
||||
* @returns {Promise} resolves once the operation is completed
|
||||
*/
|
||||
deleteOutgoingRoomKeyRequest(requestId, expectedState) {
|
||||
for (let i = 0; i < this._outgoingRoomKeyRequests.length; i++) {
|
||||
const req = this._outgoingRoomKeyRequests[i];
|
||||
public deleteOutgoingRoomKeyRequest(
|
||||
requestId: string,
|
||||
expectedState: number,
|
||||
): Promise<OutgoingRoomKeyRequest | null> {
|
||||
for (let i = 0; i < this.outgoingRoomKeyRequests.length; i++) {
|
||||
const req = this.outgoingRoomKeyRequests[i];
|
||||
|
||||
if (req.requestId !== requestId) {
|
||||
continue;
|
||||
@@ -251,7 +267,7 @@ export class MemoryCryptoStore {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
this._outgoingRoomKeyRequests.splice(i, 1);
|
||||
this.outgoingRoomKeyRequests.splice(i, 1);
|
||||
return Promise.resolve(req);
|
||||
}
|
||||
|
||||
@@ -260,48 +276,57 @@ export class MemoryCryptoStore {
|
||||
|
||||
// Olm Account
|
||||
|
||||
getAccount(txn, func) {
|
||||
func(this._account);
|
||||
public getAccount(txn: unknown, func: (accountPickle: string) => void) {
|
||||
func(this.account);
|
||||
}
|
||||
|
||||
storeAccount(txn, newData) {
|
||||
this._account = newData;
|
||||
public storeAccount(txn: unknown, accountPickle: string): void {
|
||||
this.account = accountPickle;
|
||||
}
|
||||
|
||||
getCrossSigningKeys(txn, func) {
|
||||
func(this._crossSigningKeys);
|
||||
public getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey>) => void): void {
|
||||
func(this.crossSigningKeys);
|
||||
}
|
||||
|
||||
getSecretStorePrivateKey(txn, func, type) {
|
||||
const result = this._privateKeys[type];
|
||||
public getSecretStorePrivateKey<T>(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): T {
|
||||
const result = this.privateKeys[type];
|
||||
return func(result || null);
|
||||
}
|
||||
|
||||
storeCrossSigningKeys(txn, keys) {
|
||||
this._crossSigningKeys = keys;
|
||||
public storeCrossSigningKeys(txn: unknown, keys: Record<string, ICrossSigningKey>): void {
|
||||
this.crossSigningKeys = keys;
|
||||
}
|
||||
|
||||
storeSecretStorePrivateKey(txn, type, key) {
|
||||
this._privateKeys[type] = key;
|
||||
public storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void {
|
||||
this.privateKeys[type] = key;
|
||||
}
|
||||
|
||||
// Olm Sessions
|
||||
|
||||
countEndToEndSessions(txn, func) {
|
||||
return Object.keys(this._sessions).length;
|
||||
public countEndToEndSessions(txn: unknown, func: (count: number) => void): void {
|
||||
return Object.keys(this.sessions).length;
|
||||
}
|
||||
|
||||
getEndToEndSession(deviceKey, sessionId, txn, func) {
|
||||
const deviceSessions = this._sessions[deviceKey] || {};
|
||||
public getEndToEndSession(
|
||||
deviceKey: string,
|
||||
sessionId: string,
|
||||
txn: unknown,
|
||||
func: (session: ISessionInfo) => void,
|
||||
): void {
|
||||
const deviceSessions = this.sessions[deviceKey] || {};
|
||||
func(deviceSessions[sessionId] || null);
|
||||
}
|
||||
|
||||
getEndToEndSessions(deviceKey, txn, func) {
|
||||
func(this._sessions[deviceKey] || {});
|
||||
public getEndToEndSessions(
|
||||
deviceKey: string,
|
||||
txn: unknown,
|
||||
func: (sessions: { [sessionId: string]: ISessionInfo }) => void,
|
||||
): void {
|
||||
func(this.sessions[deviceKey] || {});
|
||||
}
|
||||
|
||||
getAllEndToEndSessions(txn, func) {
|
||||
Object.entries(this._sessions).forEach(([deviceKey, deviceSessions]) => {
|
||||
public getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void {
|
||||
Object.entries(this.sessions).forEach(([deviceKey, deviceSessions]) => {
|
||||
Object.entries(deviceSessions).forEach(([sessionId, session]) => {
|
||||
func({
|
||||
...session,
|
||||
@@ -312,26 +337,25 @@ export class MemoryCryptoStore {
|
||||
});
|
||||
}
|
||||
|
||||
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
|
||||
let deviceSessions = this._sessions[deviceKey];
|
||||
public storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void {
|
||||
let deviceSessions = this.sessions[deviceKey];
|
||||
if (deviceSessions === undefined) {
|
||||
deviceSessions = {};
|
||||
this._sessions[deviceKey] = deviceSessions;
|
||||
this.sessions[deviceKey] = deviceSessions;
|
||||
}
|
||||
deviceSessions[sessionId] = sessionInfo;
|
||||
}
|
||||
|
||||
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
|
||||
const problems = this._sessionProblems[deviceKey]
|
||||
= this._sessionProblems[deviceKey] || [];
|
||||
public async storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
|
||||
const problems = this.sessionProblems[deviceKey] = this.sessionProblems[deviceKey] || [];
|
||||
problems.push({ type, fixed, time: Date.now() });
|
||||
problems.sort((a, b) => {
|
||||
return a.time - b.time;
|
||||
});
|
||||
}
|
||||
|
||||
async getEndToEndSessionProblem(deviceKey, timestamp) {
|
||||
const problems = this._sessionProblems[deviceKey] || [];
|
||||
public async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null> {
|
||||
const problems = this.sessionProblems[deviceKey] || [];
|
||||
if (!problems.length) {
|
||||
return null;
|
||||
}
|
||||
@@ -348,9 +372,9 @@ export class MemoryCryptoStore {
|
||||
}
|
||||
}
|
||||
|
||||
async filterOutNotifiedErrorDevices(devices) {
|
||||
const notifiedErrorDevices = this._notifiedErrorDevices;
|
||||
const ret = [];
|
||||
public async filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]> {
|
||||
const notifiedErrorDevices = this.notifiedErrorDevices;
|
||||
const ret: IOlmDevice[] = [];
|
||||
|
||||
for (const device of devices) {
|
||||
const { userId, deviceInfo } = device;
|
||||
@@ -370,16 +394,24 @@ export class MemoryCryptoStore {
|
||||
|
||||
// Inbound Group Sessions
|
||||
|
||||
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
|
||||
public getEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
txn: unknown,
|
||||
func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void,
|
||||
): void {
|
||||
const k = senderCurve25519Key+'/'+sessionId;
|
||||
func(
|
||||
this._inboundGroupSessions[k] || null,
|
||||
this._inboundGroupSessionsWithheld[k] || null,
|
||||
this.inboundGroupSessions[k] || null,
|
||||
this.inboundGroupSessionsWithheld[k] || null,
|
||||
);
|
||||
}
|
||||
|
||||
getAllEndToEndInboundGroupSessions(txn, func) {
|
||||
for (const key of Object.keys(this._inboundGroupSessions)) {
|
||||
public getAllEndToEndInboundGroupSessions(
|
||||
txn: unknown,
|
||||
func: (session: ISession | null) => void,
|
||||
): void {
|
||||
for (const key of Object.keys(this.inboundGroupSessions)) {
|
||||
// we can't use split, as the components we are trying to split out
|
||||
// might themselves contain '/' characters. We rely on the
|
||||
// senderKey being a (32-byte) curve25519 key, base64-encoded
|
||||
@@ -388,58 +420,71 @@ export class MemoryCryptoStore {
|
||||
func({
|
||||
senderKey: key.substr(0, 43),
|
||||
sessionId: key.substr(44),
|
||||
sessionData: this._inboundGroupSessions[key],
|
||||
sessionData: this.inboundGroupSessions[key],
|
||||
});
|
||||
}
|
||||
func(null);
|
||||
}
|
||||
|
||||
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
|
||||
public addEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: InboundGroupSessionData,
|
||||
txn: unknown,
|
||||
): void {
|
||||
const k = senderCurve25519Key+'/'+sessionId;
|
||||
if (this._inboundGroupSessions[k] === undefined) {
|
||||
this._inboundGroupSessions[k] = sessionData;
|
||||
if (this.inboundGroupSessions[k] === undefined) {
|
||||
this.inboundGroupSessions[k] = sessionData;
|
||||
}
|
||||
}
|
||||
|
||||
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
|
||||
this._inboundGroupSessions[senderCurve25519Key+'/'+sessionId] = sessionData;
|
||||
public storeEndToEndInboundGroupSession(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: InboundGroupSessionData,
|
||||
txn: unknown,
|
||||
): void {
|
||||
this.inboundGroupSessions[senderCurve25519Key+'/'+sessionId] = sessionData;
|
||||
}
|
||||
|
||||
storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key, sessionId, sessionData, txn,
|
||||
) {
|
||||
public storeEndToEndInboundGroupSessionWithheld(
|
||||
senderCurve25519Key: string,
|
||||
sessionId: string,
|
||||
sessionData: IWithheld,
|
||||
txn: unknown,
|
||||
): void {
|
||||
const k = senderCurve25519Key+'/'+sessionId;
|
||||
this._inboundGroupSessionsWithheld[k] = sessionData;
|
||||
this.inboundGroupSessionsWithheld[k] = sessionData;
|
||||
}
|
||||
|
||||
// Device Data
|
||||
|
||||
getEndToEndDeviceData(txn, func) {
|
||||
func(this._deviceData);
|
||||
public getEndToEndDeviceData(txn: unknown, func: (deviceData: IDeviceData | null) => void): void {
|
||||
func(this.deviceData);
|
||||
}
|
||||
|
||||
storeEndToEndDeviceData(deviceData, txn) {
|
||||
this._deviceData = deviceData;
|
||||
public storeEndToEndDeviceData(deviceData: IDeviceData, txn: unknown): void {
|
||||
this.deviceData = deviceData;
|
||||
}
|
||||
|
||||
// E2E rooms
|
||||
|
||||
storeEndToEndRoom(roomId, roomInfo, txn) {
|
||||
this._rooms[roomId] = roomInfo;
|
||||
public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: unknown): void {
|
||||
this.rooms[roomId] = roomInfo;
|
||||
}
|
||||
|
||||
getEndToEndRooms(txn, func) {
|
||||
func(this._rooms);
|
||||
public getEndToEndRooms(txn: unknown, func: (rooms: Record<string, IRoomEncryption>) => void): void {
|
||||
func(this.rooms);
|
||||
}
|
||||
|
||||
getSessionsNeedingBackup(limit) {
|
||||
const sessions = [];
|
||||
for (const session in this._sessionsNeedingBackup) {
|
||||
if (this._inboundGroupSessions[session]) {
|
||||
public getSessionsNeedingBackup(limit: number): Promise<ISession[]> {
|
||||
const sessions: ISession[] = [];
|
||||
for (const session in this.sessionsNeedingBackup) {
|
||||
if (this.inboundGroupSessions[session]) {
|
||||
sessions.push({
|
||||
senderKey: session.substr(0, 43),
|
||||
sessionId: session.substr(44),
|
||||
sessionData: this._inboundGroupSessions[session],
|
||||
sessionData: this.inboundGroupSessions[session],
|
||||
});
|
||||
if (limit && session.length >= limit) {
|
||||
break;
|
||||
@@ -449,39 +494,39 @@ export class MemoryCryptoStore {
|
||||
return Promise.resolve(sessions);
|
||||
}
|
||||
|
||||
countSessionsNeedingBackup() {
|
||||
return Promise.resolve(Object.keys(this._sessionsNeedingBackup).length);
|
||||
public countSessionsNeedingBackup(): Promise<number> {
|
||||
return Promise.resolve(Object.keys(this.sessionsNeedingBackup).length);
|
||||
}
|
||||
|
||||
unmarkSessionsNeedingBackup(sessions) {
|
||||
public unmarkSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
|
||||
for (const session of sessions) {
|
||||
const sessionKey = session.senderKey + '/' + session.sessionId;
|
||||
delete this._sessionsNeedingBackup[sessionKey];
|
||||
delete this.sessionsNeedingBackup[sessionKey];
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
markSessionsNeedingBackup(sessions) {
|
||||
public markSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
|
||||
for (const session of sessions) {
|
||||
const sessionKey = session.senderKey + '/' + session.sessionId;
|
||||
this._sessionsNeedingBackup[sessionKey] = true;
|
||||
this.sessionsNeedingBackup[sessionKey] = true;
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId) {
|
||||
const sessions = this._sharedHistoryInboundGroupSessions[roomId] || [];
|
||||
public addSharedHistoryInboundGroupSession(roomId: string, senderKey: string, sessionId: string): void {
|
||||
const sessions = this.sharedHistoryInboundGroupSessions[roomId] || [];
|
||||
sessions.push([senderKey, sessionId]);
|
||||
this._sharedHistoryInboundGroupSessions[roomId] = sessions;
|
||||
this.sharedHistoryInboundGroupSessions[roomId] = sessions;
|
||||
}
|
||||
|
||||
getSharedHistoryInboundGroupSessions(roomId) {
|
||||
return Promise.resolve(this._sharedHistoryInboundGroupSessions[roomId] || []);
|
||||
public getSharedHistoryInboundGroupSessions(roomId: string): Promise<[senderKey: string, sessionId: string][]> {
|
||||
return Promise.resolve(this.sharedHistoryInboundGroupSessions[roomId] || []);
|
||||
}
|
||||
|
||||
// Session key backups
|
||||
|
||||
doTxn(mode, stores, func) {
|
||||
public doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn?: unknown) => T): Promise<T> {
|
||||
return Promise.resolve(func(null));
|
||||
}
|
||||
}
|
@@ -22,8 +22,8 @@ limitations under the License.
|
||||
* @param {string} dbName The database name to test for
|
||||
* @returns {boolean} Whether the database exists
|
||||
*/
|
||||
export function exists(indexedDB, dbName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
export function exists(indexedDB: IDBFactory, dbName: string): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
let exists = true;
|
||||
const req = indexedDB.open(dbName);
|
||||
req.onupgradeneeded = () => {
|
||||
@@ -31,7 +31,7 @@ export function exists(indexedDB, dbName) {
|
||||
// should only fire if the DB did not exist before at any version.
|
||||
exists = false;
|
||||
};
|
||||
req.onblocked = () => reject();
|
||||
req.onblocked = () => reject(req.error);
|
||||
req.onsuccess = () => {
|
||||
const db = req.result;
|
||||
db.close();
|
||||
@@ -45,6 +45,6 @@ export function exists(indexedDB, dbName) {
|
||||
}
|
||||
resolve(exists);
|
||||
};
|
||||
req.onerror = ev => reject(ev.target.error);
|
||||
req.onerror = ev => reject(req.error);
|
||||
});
|
||||
}
|
@@ -62,7 +62,7 @@ log.methodFactory = function(methodName, logLevel, loggerName) {
|
||||
export const logger: PrefixedLogger = log.getLogger(DEFAULT_NAMESPACE);
|
||||
logger.setLevel(log.levels.DEBUG);
|
||||
|
||||
interface PrefixedLogger extends Logger {
|
||||
export interface PrefixedLogger extends Logger {
|
||||
withPrefix?: (prefix: string) => PrefixedLogger;
|
||||
prefix?: string;
|
||||
}
|
||||
|
@@ -117,7 +117,7 @@ function reqAsCursorPromise(req: IDBRequest<IDBCursor | null>): Promise<IDBCurso
|
||||
}
|
||||
|
||||
export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
||||
public static exists(indexedDB: IDBFactory, dbName: string): boolean {
|
||||
public static exists(indexedDB: IDBFactory, dbName: string): Promise<boolean> {
|
||||
dbName = "matrix-js-sdk:" + (dbName || "default");
|
||||
return IndexedDBHelpers.exists(indexedDB, dbName);
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@ interface IOpts extends IBaseOpts {
|
||||
}
|
||||
|
||||
export class IndexedDBStore extends MemoryStore {
|
||||
static exists(indexedDB: IDBFactory, dbName: string): boolean {
|
||||
static exists(indexedDB: IDBFactory, dbName: string): Promise<boolean> {
|
||||
return LocalIndexedDBStoreBackend.exists(indexedDB, dbName);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user