feat: complete Ancestor quote bot with production-ready Docker support

This commit is contained in:
unfunny
2026-09-13 14:14:38 -04:00
parent f2016da05a
commit fe7351a7dc
1145 changed files with 30890 additions and 97684 deletions

View File

@@ -1,118 +1,82 @@
import { type Logger } from "../../logger.ts";
import { type CrossSigningKeyInfo } from "../../crypto-api/index.ts";
import { type AESEncryptedSecretStoragePayload } from "../../@types/AESEncryptedSecretStoragePayload.ts";
import { type ISignatures } from "../../@types/signed.ts";
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 "../OlmDevice";
import { MatrixEvent } from "../../models/event";
import { DehydrationManager } from "../dehydration";
import { IEncryptedPayload } from "../aes";
/**
* Internal module. Definitions for storage for the crypto module
*/
export interface SecretStorePrivateKeys {
"m.megolm_backup.v1": AESEncryptedSecretStoragePayload;
"dehydration": {
keyInfo: DehydrationManager["keyInfo"];
key: IEncryptedPayload;
deviceDisplayName: string;
time: number;
} | null;
"m.megolm_backup.v1": IEncryptedPayload;
}
/**
* Abstraction of things that can store data required for end-to-end encryption
*/
export interface CryptoStore {
/**
* Returns true if this CryptoStore has ever been initialised (ie, it might contain data).
*
* Unlike the rest of the methods in this interface, can be called before {@link CryptoStore#startup}.
*
* @internal
*/
containsData(): Promise<boolean>;
/**
* Initialise this crypto store.
*
* Typically, this involves provisioning storage, and migrating any existing data to the current version of the
* storage schema where appropriate.
*
* Must be called before any of the rest of the methods in this interface.
*/
startup(): Promise<CryptoStore>;
deleteAllData(): Promise<void>;
/**
* Get data on how much of the libolm to Rust Crypto migration has been done.
*
* @internal
*/
getMigrationState(): Promise<MigrationState>;
/**
* Set data on how much of the libolm to Rust Crypto migration has been done.
*
* @internal
*/
setMigrationState(migrationState: MigrationState): 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>;
getAccount(txn: unknown, func: (accountPickle: string | null) => void): void;
storeAccount(txn: unknown, accountPickle: string): void;
getCrossSigningKeys(txn: unknown, func: (keys: Record<string, CrossSigningKeyInfo> | null) => void): void;
getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey> | null) => void): void;
getSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(txn: unknown, func: (key: SecretStorePrivateKeys[K] | null) => void, type: K): void;
storeCrossSigningKeys(txn: unknown, keys: Record<string, ICrossSigningKey>): void;
storeSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(txn: unknown, type: K, key: SecretStorePrivateKeys[K]): void;
countEndToEndSessions(txn: unknown, func: (count: number) => void): void;
getEndToEndSession(deviceKey: string, sessionId: string, txn: unknown, func: (session: ISessionInfo | null) => void): void;
getEndToEndSessions(deviceKey: string, txn: unknown, func: (sessions: {
[sessionId: string]: ISessionInfo;
}) => void): void;
getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo | null) => void): void;
storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void;
/**
* Get a batch of end-to-end sessions from the database.
*
* @returns A batch of Olm Sessions, or `null` if no sessions are left.
* @internal
*/
getEndToEndSessionsBatch(): Promise<ISessionInfo[] | null>;
/**
* Delete a batch of end-to-end sessions from the database.
*
* Any sessions in the list which are not found are silently ignored.
*
* @internal
*/
deleteEndToEndSessionsBatch(sessions: {
deviceKey?: string;
sessionId?: string;
}[]): Promise<void>;
storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void>;
getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null>;
filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]>;
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;
/**
* Count the number of Megolm sessions in the database.
*
* @internal
*/
countEndToEndInboundGroupSessions(): Promise<number>;
/**
* Get a batch of Megolm sessions from the database.
*
* @returns A batch of Megolm Sessions, or `null` if no sessions are left.
* @internal
*/
getEndToEndInboundGroupSessionsBatch(): Promise<SessionExtended[] | null>;
/**
* Delete a batch of Megolm sessions from the database.
*
* Any sessions in the list which are not found are silently ignored.
*
* @internal
*/
deleteEndToEndInboundGroupSessionsBatch(sessions: {
senderKey: string;
sessionId: string;
}[]): Promise<void>;
storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key: string, sessionId: string, sessionData: IWithheld, txn: unknown): void;
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>;
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: unknown) => T, log?: Logger): Promise<T>;
addSharedHistoryInboundGroupSession(roomId: string, senderKey: string, sessionId: string, txn?: unknown): void;
getSharedHistoryInboundGroupSessions(roomId: string, txn?: unknown): Promise<[senderKey: string, sessionId: string][]>;
addParkedSharedHistory(roomId: string, data: ParkedSharedHistory, txn?: unknown): void;
takeParkedSharedHistory(roomId: string, txn?: unknown): Promise<ParkedSharedHistory[]>;
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: unknown) => T, log?: PrefixedLogger): Promise<T>;
}
export type Mode = "readonly" | "readwrite";
/** Data on a Megolm session */
export interface ISession {
senderKey: string;
sessionId: string;
sessionData?: InboundGroupSessionData;
}
/** Extended data on a Megolm session */
export interface SessionExtended extends ISession {
needsBackup: boolean;
}
/** Data on an Olm session */
export interface ISessionInfo {
deviceKey?: string;
sessionId?: string;
@@ -131,6 +95,11 @@ export interface IDeviceData {
crossSigningInfo?: Record<string, ICrossSigningInfo>;
syncToken?: string;
}
export interface IProblem {
type: string;
fixed: boolean;
time: number;
}
export interface IWithheld {
room_id: string;
code: string;
@@ -163,139 +132,12 @@ export interface OutgoingRoomKeyRequest {
*/
state: RoomKeyRequestState;
}
/**
* Keys for the `account` object store to store the migration state.
* Values are defined in `MigrationState`.
* @internal
*/
export declare const ACCOUNT_OBJECT_KEY_MIGRATION_STATE = "migrationState";
/**
* A record of which steps have been completed in the libolm to Rust Crypto migration.
*
* Used by {@link CryptoStore#getMigrationState} and {@link CryptoStore#setMigrationState}.
*
* @internal
*/
export declare enum MigrationState {
/** No migration steps have yet been completed. */
NOT_STARTED = 0,
/** We have migrated the account data, cross-signing keys, etc. */
INITIAL_DATA_MIGRATED = 1,
/** INITIAL_DATA_MIGRATED, and in addition, we have migrated all the Olm sessions. */
OLM_SESSIONS_MIGRATED = 2,
/** OLM_SESSIONS_MIGRATED, and in addition, we have migrated all the Megolm sessions. */
MEGOLM_SESSIONS_MIGRATED = 3,
/** MEGOLM_SESSIONS_MIGRATED, and in addition, we have migrated all the room settings. */
ROOM_SETTINGS_MIGRATED = 4,
/** ROOM_SETTINGS_MIGRATED, and in addition, we have done the first own keys query in order to
* load the public part of the keys that have been migrated */
INITIAL_OWN_KEY_QUERY_DONE = 5
}
/**
* The size of batches to be returned by {@link CryptoStore#getEndToEndSessionsBatch} and
* {@link CryptoStore#getEndToEndInboundGroupSessionsBatch}.
*/
export declare const SESSION_BATCH_SIZE = 50;
export interface InboundGroupSessionData {
room_id: string;
/** pickled Olm.InboundGroupSession */
session: string;
keysClaimed?: Record<string, string>;
/** Devices involved in forwarding this session to us (normally empty). */
export interface ParkedSharedHistory {
senderId: string;
senderKey: string;
sessionId: string;
sessionKey: string;
keysClaimed: ReturnType<MatrixEvent["getKeysClaimed"]>;
forwardingCurve25519KeyChain: string[];
/** whether this session is untrusted. */
untrusted?: boolean;
/** whether this session exists during the room being set to shared history. */
sharedHistory?: boolean;
}
export interface ICrossSigningInfo {
keys: Record<string, CrossSigningKeyInfo>;
firstUse: boolean;
crossSigningVerifiedBefore: boolean;
}
export interface IRoomEncryption {
algorithm: string;
rotation_period_ms?: number;
rotation_period_msgs?: number;
}
export declare enum TrackingStatus {
NotTracked = 0,
PendingDownload = 1,
DownloadInProgress = 2,
UpToDate = 3
}
/**
* possible states for a room key request
*
* The state machine looks like:
* ```
*
* | (cancellation sent)
* | .-------------------------------------------------.
* | | |
* V V (cancellation requested) |
* UNSENT -----------------------------+ |
* | | |
* | | |
* | (send successful) | CANCELLATION_PENDING_AND_WILL_RESEND
* V | Λ
* SENT | |
* |-------------------------------- | --------------'
* | | (cancellation requested with intent
* | | to resend the original request)
* | |
* | (cancellation requested) |
* V |
* CANCELLATION_PENDING |
* | |
* | (cancellation sent) |
* V |
* (deleted) <---------------------------+
* ```
*/
export declare enum RoomKeyRequestState {
/** request not yet sent */
Unsent = 0,
/** request sent, awaiting reply */
Sent = 1,
/** reply received, cancellation not yet sent */
CancellationPending = 2,
/**
* Cancellation not yet sent and will transition to UNSENT instead of
* being deleted once the cancellation has been sent.
*/
CancellationPendingAndWillResend = 3
}
interface IRoomKey {
room_id: string;
algorithm: string;
}
/**
* The parameters of a room key request. The details of the request may
* vary with the crypto algorithm, but the management and storage layers for
* outgoing requests expect it to have 'room_id' and 'session_id' properties.
*/
export interface IRoomKeyRequestBody extends IRoomKey {
session_id: string;
sender_key: string;
}
export interface IRoomKeyRequestRecipient {
userId: string;
deviceId: string;
}
interface IDevice {
keys: Record<string, string>;
algorithms: string[];
verified: DeviceVerification;
known: boolean;
unsigned?: Record<string, any>;
signatures?: ISignatures;
}
/** State of the verification of the device. */
export declare enum DeviceVerification {
Blocked = -1,
Unverified = 0,
Verified = 1
}
export {};
//# sourceMappingURL=base.d.ts.map

File diff suppressed because one or more lines are too long

View File

@@ -1,136 +1,6 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
"use strict";
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.
*/
/**
* Internal module. Definitions for storage for the crypto module
*/
/**
* Abstraction of things that can store data required for end-to-end encryption
*/
/** Data on a Megolm session */
/** Extended data on a Megolm session */
/** Data on an Olm session */
/**
* Represents an outgoing room key request
*/
/**
* Keys for the `account` object store to store the migration state.
* Values are defined in `MigrationState`.
* @internal
*/
export const ACCOUNT_OBJECT_KEY_MIGRATION_STATE = "migrationState";
/**
* A record of which steps have been completed in the libolm to Rust Crypto migration.
*
* Used by {@link CryptoStore#getMigrationState} and {@link CryptoStore#setMigrationState}.
*
* @internal
*/
export let MigrationState = /*#__PURE__*/function (MigrationState) {
/** No migration steps have yet been completed. */
MigrationState[MigrationState["NOT_STARTED"] = 0] = "NOT_STARTED";
/** We have migrated the account data, cross-signing keys, etc. */
MigrationState[MigrationState["INITIAL_DATA_MIGRATED"] = 1] = "INITIAL_DATA_MIGRATED";
/** INITIAL_DATA_MIGRATED, and in addition, we have migrated all the Olm sessions. */
MigrationState[MigrationState["OLM_SESSIONS_MIGRATED"] = 2] = "OLM_SESSIONS_MIGRATED";
/** OLM_SESSIONS_MIGRATED, and in addition, we have migrated all the Megolm sessions. */
MigrationState[MigrationState["MEGOLM_SESSIONS_MIGRATED"] = 3] = "MEGOLM_SESSIONS_MIGRATED";
/** MEGOLM_SESSIONS_MIGRATED, and in addition, we have migrated all the room settings. */
MigrationState[MigrationState["ROOM_SETTINGS_MIGRATED"] = 4] = "ROOM_SETTINGS_MIGRATED";
/** ROOM_SETTINGS_MIGRATED, and in addition, we have done the first own keys query in order to
* load the public part of the keys that have been migrated */
MigrationState[MigrationState["INITIAL_OWN_KEY_QUERY_DONE"] = 5] = "INITIAL_OWN_KEY_QUERY_DONE";
return MigrationState;
}({});
/**
* The size of batches to be returned by {@link CryptoStore#getEndToEndSessionsBatch} and
* {@link CryptoStore#getEndToEndInboundGroupSessionsBatch}.
*/
export const SESSION_BATCH_SIZE = 50;
export let TrackingStatus = /*#__PURE__*/function (TrackingStatus) {
TrackingStatus[TrackingStatus["NotTracked"] = 0] = "NotTracked";
TrackingStatus[TrackingStatus["PendingDownload"] = 1] = "PendingDownload";
TrackingStatus[TrackingStatus["DownloadInProgress"] = 2] = "DownloadInProgress";
TrackingStatus[TrackingStatus["UpToDate"] = 3] = "UpToDate";
return TrackingStatus;
}({});
/**
* possible states for a room key request
*
* The state machine looks like:
* ```
*
* | (cancellation sent)
* | .-------------------------------------------------.
* | | |
* V V (cancellation requested) |
* UNSENT -----------------------------+ |
* | | |
* | | |
* | (send successful) | CANCELLATION_PENDING_AND_WILL_RESEND
* V | Λ
* SENT | |
* |-------------------------------- | --------------'
* | | (cancellation requested with intent
* | | to resend the original request)
* | |
* | (cancellation requested) |
* V |
* CANCELLATION_PENDING |
* | |
* | (cancellation sent) |
* V |
* (deleted) <---------------------------+
* ```
*/
export let RoomKeyRequestState = /*#__PURE__*/function (RoomKeyRequestState) {
/** request not yet sent */
RoomKeyRequestState[RoomKeyRequestState["Unsent"] = 0] = "Unsent";
/** request sent, awaiting reply */
RoomKeyRequestState[RoomKeyRequestState["Sent"] = 1] = "Sent";
/** reply received, cancellation not yet sent */
RoomKeyRequestState[RoomKeyRequestState["CancellationPending"] = 2] = "CancellationPending";
/**
* Cancellation not yet sent and will transition to UNSENT instead of
* being deleted once the cancellation has been sent.
*/
RoomKeyRequestState[RoomKeyRequestState["CancellationPendingAndWillResend"] = 3] = "CancellationPendingAndWillResend";
return RoomKeyRequestState;
}({});
/**
* The parameters of a room key request. The details of the request may
* vary with the crypto algorithm, but the management and storage layers for
* outgoing requests expect it to have 'room_id' and 'session_id' properties.
*/
/** State of the verification of the device. */
export let DeviceVerification = /*#__PURE__*/function (DeviceVerification) {
DeviceVerification[DeviceVerification["Blocked"] = -1] = "Blocked";
DeviceVerification[DeviceVerification["Unverified"] = 0] = "Unverified";
DeviceVerification[DeviceVerification["Verified"] = 1] = "Verified";
return DeviceVerification;
}({});
Object.defineProperty(exports, "__esModule", {
value: true
});
//# sourceMappingURL=base.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,14 @@
import { type Logger } from "../../logger.ts";
import { type CryptoStore, type IDeviceData, type ISession, type SessionExtended, type ISessionInfo, type IWithheld, MigrationState, type Mode, type SecretStorePrivateKeys, type InboundGroupSessionData, type IRoomEncryption } from "./base.ts";
import { type CrossSigningKeyInfo } from "../../crypto-api/index.ts";
import { PrefixedLogger } from "../../logger";
import { CryptoStore, IDeviceData, IProblem, ISession, ISessionInfo, IWithheld, Mode, OutgoingRoomKeyRequest, ParkedSharedHistory, SecretStorePrivateKeys } from "./base";
import { IRoomKeyRequestBody } from "../index";
import { ICrossSigningKey } from "../../client";
import { IOlmDevice } from "../algorithms/megolm";
import { IRoomEncryption } from "../RoomList";
import { InboundGroupSessionData } from "../OlmDevice";
/**
* Implementation of a CryptoStore which is backed by an existing
* IndexedDB connection. Generally you want IndexedDBCryptoStore
* which connects to the database and defers to one of these.
*
* @internal
*/
export declare class Backend implements CryptoStore {
private db;
@@ -14,80 +16,114 @@ export declare class Backend implements CryptoStore {
/**
*/
constructor(db: IDBDatabase);
containsData(): Promise<boolean>;
startup(): Promise<CryptoStore>;
deleteAllData(): Promise<void>;
/**
* Get data on how much of the libolm to Rust Crypto migration has been done.
* Look for an existing outgoing room key request, and if none is found,
* add a new one
*
* Implementation of {@link CryptoStore.getMigrationState}.
*
* @returns resolves to
* {@link OutgoingRoomKeyRequest}: either the
* same instance as passed in, or the existing one.
*/
getMigrationState(): Promise<MigrationState>;
getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise<OutgoingRoomKeyRequest>;
/**
* Set data on how much of the libolm to Rust Crypto migration has been done.
* Look for an existing room key request
*
* Implementation of {@link CryptoStore.setMigrationState}.
* @param requestBody - existing request to look for
*
* @returns resolves to the matching
* {@link OutgoingRoomKeyRequest}, or null if
* not found
*/
setMigrationState(migrationState: MigrationState): Promise<void>;
getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<OutgoingRoomKeyRequest | null>;
/**
* look for an existing room key request in the db
*
* @internal
* @param txn - database transaction
* @param requestBody - existing request to look for
* @param callback - function to call with the results of the
* search. Either passed a matching
* {@link OutgoingRoomKeyRequest}, or null if
* not found.
*/
private _getOutgoingRoomKeyRequest;
/**
* Look for room key requests by state
*
* @param wantedStates - list of acceptable states
*
* @returns resolves to the a
* {@link OutgoingRoomKeyRequest}, or null if
* there are no pending requests in those states. If there are multiple
* requests in those states, an arbitrary one is chosen.
*/
getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise<OutgoingRoomKeyRequest | null>;
/**
*
* @returns All elements in a given state
*/
getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise<OutgoingRoomKeyRequest[]>;
getOutgoingRoomKeyRequestsByTarget(userId: string, deviceId: string, wantedStates: number[]): Promise<OutgoingRoomKeyRequest[]>;
/**
* Look for an existing room key request by id and state, and update it if
* found
*
* @param requestId - ID of request to update
* @param expectedState - state we expect to find the request in
* @param updates - name/value map of updates to apply
*
* @returns resolves to
* {@link OutgoingRoomKeyRequest}
* updated request, or null if no matching row was found
*/
updateOutgoingRoomKeyRequest(requestId: string, expectedState: number, updates: Partial<OutgoingRoomKeyRequest>): Promise<OutgoingRoomKeyRequest | null>;
/**
* Look for an existing room key request by id and state, and delete it if
* found
*
* @param requestId - ID of request to update
* @param expectedState - state we expect to find the request in
*
* @returns resolves once the operation is completed
*/
deleteOutgoingRoomKeyRequest(requestId: string, expectedState: number): Promise<OutgoingRoomKeyRequest | null>;
getAccount(txn: IDBTransaction, func: (accountPickle: string | null) => void): void;
storeAccount(txn: IDBTransaction, accountPickle: string): void;
getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record<string, CrossSigningKeyInfo> | null) => void): void;
getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record<string, ICrossSigningKey> | null) => void): void;
getSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(txn: IDBTransaction, func: (key: SecretStorePrivateKeys[K] | null) => void, type: K): void;
storeCrossSigningKeys(txn: IDBTransaction, keys: Record<string, ICrossSigningKey>): void;
storeSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(txn: IDBTransaction, type: K, key: SecretStorePrivateKeys[K]): void;
countEndToEndSessions(txn: IDBTransaction, func: (count: number) => void): void;
getEndToEndSessions(deviceKey: string, txn: IDBTransaction, func: (sessions: {
[sessionId: string]: ISessionInfo;
}) => void): void;
getEndToEndSession(deviceKey: string, sessionId: string, txn: IDBTransaction, func: (session: ISessionInfo | null) => void): void;
getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo | null) => void): void;
storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: IDBTransaction): void;
/**
* Fetch a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndSessionsBatch}.
*/
getEndToEndSessionsBatch(): Promise<null | ISessionInfo[]>;
/**
* Delete a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndSessionsBatch}.
*
* @internal
*/
deleteEndToEndSessionsBatch(sessions: {
deviceKey: string;
sessionId: string;
}[]): Promise<void>;
storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void>;
getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null>;
filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]>;
getEndToEndInboundGroupSession(senderCurve25519Key: string, sessionId: string, txn: IDBTransaction, func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void): void;
getAllEndToEndInboundGroupSessions(txn: IDBTransaction, func: (session: ISession | null) => void): void;
addEndToEndInboundGroupSession(senderCurve25519Key: string, sessionId: string, sessionData: InboundGroupSessionData, txn: IDBTransaction): void;
storeEndToEndInboundGroupSession(senderCurve25519Key: string, sessionId: string, sessionData: InboundGroupSessionData, txn: IDBTransaction): void;
/**
* Count the number of Megolm sessions in the database.
*
* Implementation of {@link CryptoStore.countEndToEndInboundGroupSessions}.
*
* @internal
*/
countEndToEndInboundGroupSessions(): Promise<number>;
/**
* Fetch a batch of Megolm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndInboundGroupSessionsBatch}.
*/
getEndToEndInboundGroupSessionsBatch(): Promise<null | SessionExtended[]>;
/**
* Delete a batch of Megolm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndInboundGroupSessionsBatch}.
*
* @internal
*/
deleteEndToEndInboundGroupSessionsBatch(sessions: {
senderKey: string;
sessionId: string;
}[]): Promise<void>;
storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key: string, sessionId: string, sessionData: IWithheld, txn: IDBTransaction): void;
getEndToEndDeviceData(txn: IDBTransaction, func: (deviceData: IDeviceData | null) => void): void;
storeEndToEndDeviceData(deviceData: IDeviceData, txn: IDBTransaction): void;
storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: IDBTransaction): void;
getEndToEndRooms(txn: IDBTransaction, func: (rooms: Record<string, IRoomEncryption>) => void): void;
getSessionsNeedingBackup(limit: number): Promise<ISession[]>;
countSessionsNeedingBackup(txn?: IDBTransaction): Promise<number>;
unmarkSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void>;
markSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void>;
doTxn<T>(mode: Mode, stores: string | string[], func: (txn: IDBTransaction) => T, log?: Logger): Promise<T>;
addSharedHistoryInboundGroupSession(roomId: string, senderKey: string, sessionId: string, txn?: IDBTransaction): void;
getSharedHistoryInboundGroupSessions(roomId: string, txn?: IDBTransaction): Promise<[senderKey: string, sessionId: string][]>;
addParkedSharedHistory(roomId: string, parkedData: ParkedSharedHistory, txn?: IDBTransaction): void;
takeParkedSharedHistory(roomId: string, txn?: IDBTransaction): Promise<ParkedSharedHistory[]>;
doTxn<T>(mode: Mode, stores: string | string[], func: (txn: IDBTransaction) => T, log?: PrefixedLogger): Promise<T>;
}
export declare const VERSION: number;
export declare function upgradeDatabase(db: IDBDatabase, oldVersion: number): void;

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,14 @@
import _defineProperty from "@babel/runtime/helpers/defineProperty";
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.VERSION = exports.Backend = void 0;
exports.upgradeDatabase = upgradeDatabase;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _logger = require("../../logger");
var _utils = require("../../utils");
/*
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
@@ -15,35 +25,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger } from "../../logger.js";
import { MigrationState, SESSION_BATCH_SIZE, ACCOUNT_OBJECT_KEY_MIGRATION_STATE } from "./base.js";
import { IndexedDBCryptoStore } from "./indexeddb-crypto-store.js";
const PROFILE_TRANSACTIONS = false;
/**
* Implementation of a CryptoStore which is backed by an existing
* IndexedDB connection. Generally you want IndexedDBCryptoStore
* which connects to the database and defers to one of these.
*
* @internal
*/
export class Backend {
class Backend {
/**
*/
constructor(db) {
_defineProperty(this, "nextTxnId", 0);
this.db = db;
(0, _defineProperty2.default)(this, "nextTxnId", 0);
// 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 = () => {
logger.log(`versionchange for indexeddb ${this.db.name}: closing`);
_logger.logger.log(`versionchange for indexeddb ${this.db.name}: closing`);
db.close();
};
}
async containsData() {
throw Error("Not implemented for Backend");
}
async startup() {
// No work to do, as the startup is done by the caller (e.g IndexedDBCryptoStore)
// by passing us a ready IDBDatabase instance
@@ -54,34 +56,249 @@ export class Backend {
}
/**
* Get data on how much of the libolm to Rust Crypto migration has been done.
* Look for an existing outgoing room key request, and if none is found,
* add a new one
*
* Implementation of {@link CryptoStore.getMigrationState}.
*
* @returns resolves to
* {@link OutgoingRoomKeyRequest}: either the
* same instance as passed in, or the existing one.
*/
async getMigrationState() {
let migrationState = MigrationState.NOT_STARTED;
await this.doTxn("readonly", [IndexedDBCryptoStore.STORE_ACCOUNT], txn => {
const objectStore = txn.objectStore(IndexedDBCryptoStore.STORE_ACCOUNT);
const getReq = objectStore.get(ACCOUNT_OBJECT_KEY_MIGRATION_STATE);
getReq.onsuccess = () => {
migrationState = getReq.result ?? MigrationState.NOT_STARTED;
};
getOrAddOutgoingRoomKeyRequest(request) {
const requestBody = request.requestBody;
return new Promise((resolve, reject) => {
const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite");
txn.onerror = reject;
// first see if we already have an entry for this request.
this._getOutgoingRoomKeyRequest(txn, requestBody, existing => {
if (existing) {
// this entry matches the request - return it.
_logger.logger.log(`already have key request outstanding for ` + `${requestBody.room_id} / ${requestBody.session_id}: ` + `not sending another`);
resolve(existing);
return;
}
// we got to the end of the list without finding a match
// - add the new request.
_logger.logger.log(`enqueueing key request for ${requestBody.room_id} / ` + requestBody.session_id);
txn.oncomplete = () => {
resolve(request);
};
const store = txn.objectStore("outgoingRoomKeyRequests");
store.add(request);
});
});
return migrationState;
}
/**
* Set data on how much of the libolm to Rust Crypto migration has been done.
* Look for an existing room key request
*
* Implementation of {@link CryptoStore.setMigrationState}.
* @param requestBody - existing request to look for
*
* @returns resolves to the matching
* {@link OutgoingRoomKeyRequest}, or null if
* not found
*/
async setMigrationState(migrationState) {
await this.doTxn("readwrite", [IndexedDBCryptoStore.STORE_ACCOUNT], txn => {
const objectStore = txn.objectStore(IndexedDBCryptoStore.STORE_ACCOUNT);
objectStore.put(migrationState, ACCOUNT_OBJECT_KEY_MIGRATION_STATE);
getOutgoingRoomKeyRequest(requestBody) {
return new Promise((resolve, reject) => {
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
txn.onerror = reject;
this._getOutgoingRoomKeyRequest(txn, requestBody, existing => {
resolve(existing);
});
});
}
/**
* look for an existing room key request in the db
*
* @internal
* @param txn - database transaction
* @param requestBody - existing request to look for
* @param callback - function to call with the results of the
* search. Either passed a matching
* {@link OutgoingRoomKeyRequest}, or null if
* not found.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
_getOutgoingRoomKeyRequest(txn, requestBody, callback) {
const store = txn.objectStore("outgoingRoomKeyRequests");
const idx = store.index("session");
const cursorReq = idx.openCursor([requestBody.room_id, requestBody.session_id]);
cursorReq.onsuccess = () => {
const cursor = cursorReq.result;
if (!cursor) {
// no match found
callback(null);
return;
}
const existing = cursor.value;
if ((0, _utils.deepCompare)(existing.requestBody, requestBody)) {
// got a match
callback(existing);
return;
}
// look at the next entry in the index
cursor.continue();
};
}
/**
* Look for room key requests by state
*
* @param wantedStates - list of acceptable states
*
* @returns resolves to the a
* {@link OutgoingRoomKeyRequest}, or null if
* there are no pending requests in those states. If there are multiple
* requests in those states, an arbitrary one is chosen.
*/
getOutgoingRoomKeyRequestByState(wantedStates) {
if (wantedStates.length === 0) {
return Promise.resolve(null);
}
// this is a bit tortuous because we need to make sure we do the lookup
// in a single transaction, to avoid having a race with the insertion
// code.
// index into the wantedStates array
let stateIndex = 0;
let result;
function onsuccess() {
const cursor = this.result;
if (cursor) {
// got a match
result = cursor.value;
return;
}
// try the next state in the list
stateIndex++;
if (stateIndex >= wantedStates.length) {
// no matches
return;
}
const wantedState = wantedStates[stateIndex];
const cursorReq = this.source.openCursor(wantedState);
cursorReq.onsuccess = onsuccess;
}
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
const store = txn.objectStore("outgoingRoomKeyRequests");
const wantedState = wantedStates[stateIndex];
const cursorReq = store.index("state").openCursor(wantedState);
cursorReq.onsuccess = onsuccess;
return promiseifyTxn(txn).then(() => result);
}
/**
*
* @returns All elements in a given state
*/
getAllOutgoingRoomKeyRequestsByState(wantedState) {
return new Promise((resolve, reject) => {
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
const store = txn.objectStore("outgoingRoomKeyRequests");
const index = store.index("state");
const request = index.getAll(wantedState);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
let stateIndex = 0;
const results = [];
function onsuccess() {
const cursor = this.result;
if (cursor) {
const keyReq = cursor.value;
if (keyReq.recipients.some(recipient => recipient.userId === userId && recipient.deviceId === deviceId)) {
results.push(keyReq);
}
cursor.continue();
} else {
// try the next state in the list
stateIndex++;
if (stateIndex >= wantedStates.length) {
// no matches
return;
}
const wantedState = wantedStates[stateIndex];
const cursorReq = this.source.openCursor(wantedState);
cursorReq.onsuccess = onsuccess;
}
}
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
const store = txn.objectStore("outgoingRoomKeyRequests");
const wantedState = wantedStates[stateIndex];
const cursorReq = store.index("state").openCursor(wantedState);
cursorReq.onsuccess = onsuccess;
return promiseifyTxn(txn).then(() => results);
}
/**
* Look for an existing room key request by id and state, and update it if
* found
*
* @param requestId - ID of request to update
* @param expectedState - state we expect to find the request in
* @param updates - name/value map of updates to apply
*
* @returns resolves to
* {@link OutgoingRoomKeyRequest}
* updated request, or null if no matching row was found
*/
updateOutgoingRoomKeyRequest(requestId, expectedState, updates) {
let result = null;
function onsuccess() {
const cursor = this.result;
if (!cursor) {
return;
}
const data = cursor.value;
if (data.state != expectedState) {
_logger.logger.warn(`Cannot update room key request from ${expectedState} ` + `as it was already updated to ${data.state}`);
return;
}
Object.assign(data, updates);
cursor.update(data);
result = data;
}
const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite");
const cursorReq = txn.objectStore("outgoingRoomKeyRequests").openCursor(requestId);
cursorReq.onsuccess = onsuccess;
return promiseifyTxn(txn).then(() => result);
}
/**
* Look for an existing room key request by id and state, and delete it if
* found
*
* @param requestId - ID of request to update
* @param expectedState - state we expect to find the request in
*
* @returns 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 = () => {
const cursor = cursorReq.result;
if (!cursor) {
return;
}
const data = cursor.value;
if (data.state != expectedState) {
_logger.logger.warn(`Cannot delete room key request in state ${data.state} ` + `(expected ${expectedState})`);
return;
}
cursor.delete();
};
return promiseifyTxn(txn);
}
// Olm Account
getAccount(txn, func) {
@@ -121,6 +338,10 @@ export class Backend {
}
};
}
storeCrossSigningKeys(txn, keys) {
const objectStore = txn.objectStore("account");
objectStore.put(keys, "crossSigningKeys");
}
storeSecretStorePrivateKey(txn, type, key) {
const objectStore = txn.objectStore("account");
objectStore.put(key, `ssss_cache:${type}`);
@@ -179,6 +400,23 @@ export class Backend {
}
};
}
getAllEndToEndSessions(txn, func) {
const objectStore = txn.objectStore("sessions");
const getReq = objectStore.openCursor();
getReq.onsuccess = function () {
try {
const cursor = getReq.result;
if (cursor) {
func(cursor.value);
cursor.continue();
} else {
func(null);
}
} catch (e) {
abortWithException(txn, e);
}
};
}
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
const objectStore = txn.objectStore("sessions");
objectStore.put({
@@ -188,62 +426,76 @@ export class Backend {
lastReceivedMessageTs: sessionInfo.lastReceivedMessageTs
});
}
/**
* Fetch a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndSessionsBatch}.
*/
async getEndToEndSessionsBatch() {
const result = [];
await this.doTxn("readonly", [IndexedDBCryptoStore.STORE_SESSIONS], txn => {
const objectStore = txn.objectStore(IndexedDBCryptoStore.STORE_SESSIONS);
const getReq = objectStore.openCursor();
getReq.onsuccess = function () {
try {
const cursor = getReq.result;
if (cursor) {
result.push(cursor.value);
if (result.length < SESSION_BATCH_SIZE) {
cursor.continue();
}
}
} catch (e) {
abortWithException(txn, e);
}
};
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
const txn = this.db.transaction("session_problems", "readwrite");
const objectStore = txn.objectStore("session_problems");
objectStore.put({
deviceKey,
type,
fixed,
time: Date.now()
});
if (result.length === 0) {
// No sessions left.
return null;
}
await promiseifyTxn(txn);
}
async getEndToEndSessionProblem(deviceKey, timestamp) {
let result = null;
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 = () => {
const problems = req.result;
if (!problems.length) {
result = null;
return;
}
problems.sort((a, b) => {
return a.time - b.time;
});
const lastProblem = problems[problems.length - 1];
for (const problem of problems) {
if (problem.time > timestamp) {
result = Object.assign({}, problem, {
fixed: lastProblem.fixed
});
return;
}
}
if (lastProblem.fixed) {
result = null;
} else {
result = lastProblem;
}
};
await promiseifyTxn(txn);
return result;
}
/**
* Delete a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndSessionsBatch}.
*
* @internal
*/
async deleteEndToEndSessionsBatch(sessions) {
await this.doTxn("readwrite", [IndexedDBCryptoStore.STORE_SESSIONS], async txn => {
try {
const objectStore = txn.objectStore(IndexedDBCryptoStore.STORE_SESSIONS);
for (const {
deviceKey,
sessionId
} of sessions) {
const req = objectStore.delete([deviceKey, sessionId]);
await new Promise(resolve => {
req.onsuccess = resolve;
});
}
} catch (e) {
abortWithException(txn, e);
}
});
// FIXME: we should probably prune this when devices get deleted
async filterOutNotifiedErrorDevices(devices) {
const txn = this.db.transaction("notified_error_devices", "readwrite");
const objectStore = txn.objectStore("notified_error_devices");
const ret = [];
await Promise.all(devices.map(device => {
return new Promise(resolve => {
const {
userId,
deviceInfo
} = device;
const getReq = objectStore.get([userId, deviceInfo.deviceId]);
getReq.onsuccess = function () {
if (!getReq.result) {
objectStore.put({
userId,
deviceId: deviceInfo.deviceId
});
ret.push(device);
}
resolve();
};
});
}));
return ret;
}
// Inbound group sessions
@@ -284,6 +536,51 @@ export class Backend {
}
};
}
getAllEndToEndInboundGroupSessions(txn, func) {
const objectStore = txn.objectStore("inbound_group_sessions");
const getReq = objectStore.openCursor();
getReq.onsuccess = function () {
const cursor = getReq.result;
if (cursor) {
try {
func({
senderKey: cursor.value.senderCurve25519Key,
sessionId: cursor.value.sessionId,
sessionData: cursor.value.session
});
} catch (e) {
abortWithException(txn, e);
}
cursor.continue();
} else {
try {
func(null);
} catch (e) {
abortWithException(txn, e);
}
}
};
}
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
const objectStore = txn.objectStore("inbound_group_sessions");
const addReq = objectStore.add({
senderCurve25519Key,
sessionId,
session: sessionData
});
addReq.onerror = ev => {
var _addReq$error;
if (((_addReq$error = addReq.error) === null || _addReq$error === void 0 ? void 0 : _addReq$error.name) === "ConstraintError") {
// This stops the error from triggering the txn's onerror
ev.stopPropagation();
// ...and this stops it from aborting the transaction
ev.preventDefault();
_logger.logger.log("Ignoring duplicate inbound group session: " + senderCurve25519Key + " / " + sessionId);
} else {
abortWithException(txn, new Error("Failed to add inbound group session: " + addReq.error));
}
};
}
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
const objectStore = txn.objectStore("inbound_group_sessions");
objectStore.put({
@@ -292,89 +589,12 @@ export class Backend {
session: sessionData
});
}
/**
* Count the number of Megolm sessions in the database.
*
* Implementation of {@link CryptoStore.countEndToEndInboundGroupSessions}.
*
* @internal
*/
async countEndToEndInboundGroupSessions() {
let result = 0;
await this.doTxn("readonly", [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], txn => {
const sessionStore = txn.objectStore(IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS);
const countReq = sessionStore.count();
countReq.onsuccess = () => {
result = countReq.result;
};
});
return result;
}
/**
* Fetch a batch of Megolm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndInboundGroupSessionsBatch}.
*/
async getEndToEndInboundGroupSessionsBatch() {
const result = [];
await this.doTxn("readonly", [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS, IndexedDBCryptoStore.STORE_BACKUP], txn => {
const sessionStore = txn.objectStore(IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS);
const backupStore = txn.objectStore(IndexedDBCryptoStore.STORE_BACKUP);
const getReq = sessionStore.openCursor();
getReq.onsuccess = function () {
try {
const cursor = getReq.result;
if (cursor) {
const backupGetReq = backupStore.get(cursor.key);
backupGetReq.onsuccess = () => {
result.push({
senderKey: cursor.value.senderCurve25519Key,
sessionId: cursor.value.sessionId,
sessionData: cursor.value.session,
needsBackup: backupGetReq.result !== undefined
});
if (result.length < SESSION_BATCH_SIZE) {
cursor.continue();
}
};
}
} catch (e) {
abortWithException(txn, e);
}
};
});
if (result.length === 0) {
// No sessions left.
return null;
}
return result;
}
/**
* Delete a batch of Megolm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndInboundGroupSessionsBatch}.
*
* @internal
*/
async deleteEndToEndInboundGroupSessionsBatch(sessions) {
await this.doTxn("readwrite", [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], async txn => {
try {
const objectStore = txn.objectStore(IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS);
for (const {
senderKey,
sessionId
} of sessions) {
const req = objectStore.delete([senderKey, sessionId]);
await new Promise(resolve => {
req.onsuccess = resolve;
});
}
} catch (e) {
abortWithException(txn, e);
}
storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn) {
const objectStore = txn.objectStore("inbound_group_sessions_withheld");
objectStore.put({
senderCurve25519Key,
sessionId,
session: sessionData
});
}
getEndToEndDeviceData(txn, func) {
@@ -388,6 +608,14 @@ export class Backend {
}
};
}
storeEndToEndDeviceData(deviceData, txn) {
const objectStore = txn.objectStore("device_data");
objectStore.put(deviceData, "-");
}
storeEndToEndRoom(roomId, roomInfo, txn) {
const objectStore = txn.objectStore("rooms");
objectStore.put(roomInfo, roomId);
}
getEndToEndRooms(txn, func) {
const rooms = {};
const objectStore = txn.objectStore("rooms");
@@ -406,6 +634,62 @@ export class Backend {
}
};
}
// session backups
getSessionsNeedingBackup(limit) {
return new Promise((resolve, reject) => {
const sessions = [];
const txn = this.db.transaction(["sessions_needing_backup", "inbound_group_sessions"], "readonly");
txn.onerror = reject;
txn.oncomplete = function () {
resolve(sessions);
};
const objectStore = txn.objectStore("sessions_needing_backup");
const sessionStore = txn.objectStore("inbound_group_sessions");
const getReq = objectStore.openCursor();
getReq.onsuccess = function () {
const cursor = getReq.result;
if (cursor) {
const sessionGetReq = sessionStore.get(cursor.key);
sessionGetReq.onsuccess = function () {
sessions.push({
senderKey: sessionGetReq.result.senderCurve25519Key,
sessionId: sessionGetReq.result.sessionId,
sessionData: sessionGetReq.result.session
});
};
if (!limit || sessions.length < limit) {
cursor.continue();
}
}
};
});
}
countSessionsNeedingBackup(txn) {
if (!txn) {
txn = this.db.transaction("sessions_needing_backup", "readonly");
}
const objectStore = txn.objectStore("sessions_needing_backup");
return new Promise((resolve, reject) => {
const req = objectStore.count();
req.onerror = reject;
req.onsuccess = () => resolve(req.result);
});
}
async unmarkSessionsNeedingBackup(sessions, txn) {
if (!txn) {
txn = this.db.transaction("sessions_needing_backup", "readwrite");
}
const objectStore = txn.objectStore("sessions_needing_backup");
await Promise.all(sessions.map(session => {
return new Promise((resolve, reject) => {
const req = objectStore.delete([session.senderKey, session.sessionId]);
req.onsuccess = resolve;
req.onerror = reject;
});
}));
}
async markSessionsNeedingBackup(sessions, txn) {
if (!txn) {
txn = this.db.transaction("sessions_needing_backup", "readwrite");
@@ -422,7 +706,82 @@ export class Backend {
});
}));
}
doTxn(mode, stores, func, log = logger) {
addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn) {
if (!txn) {
txn = this.db.transaction("shared_history_inbound_group_sessions", "readwrite");
}
const objectStore = txn.objectStore("shared_history_inbound_group_sessions");
const req = objectStore.get([roomId]);
req.onsuccess = () => {
const {
sessions
} = req.result || {
sessions: []
};
sessions.push([senderKey, sessionId]);
objectStore.put({
roomId,
sessions
});
};
}
getSharedHistoryInboundGroupSessions(roomId, txn) {
if (!txn) {
txn = this.db.transaction("shared_history_inbound_group_sessions", "readonly");
}
const objectStore = txn.objectStore("shared_history_inbound_group_sessions");
const req = objectStore.get([roomId]);
return new Promise((resolve, reject) => {
req.onsuccess = () => {
const {
sessions
} = req.result || {
sessions: []
};
resolve(sessions);
};
req.onerror = reject;
});
}
addParkedSharedHistory(roomId, parkedData, txn) {
if (!txn) {
txn = this.db.transaction("parked_shared_history", "readwrite");
}
const objectStore = txn.objectStore("parked_shared_history");
const req = objectStore.get([roomId]);
req.onsuccess = () => {
const {
parked
} = req.result || {
parked: []
};
parked.push(parkedData);
objectStore.put({
roomId,
parked
});
};
}
takeParkedSharedHistory(roomId, txn) {
if (!txn) {
txn = this.db.transaction("parked_shared_history", "readwrite");
}
const cursorReq = txn.objectStore("parked_shared_history").openCursor(roomId);
return new Promise((resolve, reject) => {
cursorReq.onsuccess = () => {
const cursor = cursorReq.result;
if (!cursor) {
resolve([]);
return;
}
const data = cursor.value;
cursor.delete();
resolve(data);
};
cursorReq.onerror = reject;
});
}
doTxn(mode, stores, func, log = _logger.logger) {
let startTime;
let description;
if (PROFILE_TRANSACTIONS) {
@@ -448,6 +807,7 @@ export class Backend {
});
}
}
exports.Backend = Backend;
const DB_MIGRATIONS = [db => {
createDatabase(db);
}, db => {
@@ -492,9 +852,11 @@ const DB_MIGRATIONS = [db => {
}
// Expand as needed.
];
export const VERSION = DB_MIGRATIONS.length;
export function upgradeDatabase(db, oldVersion) {
logger.log(`Upgrading IndexedDBCryptoStore from version ${oldVersion} to ${VERSION}`);
const VERSION = DB_MIGRATIONS.length;
exports.VERSION = VERSION;
function upgradeDatabase(db, oldVersion) {
_logger.logger.log(`Upgrading IndexedDBCryptoStore from version ${oldVersion}` + ` to ${VERSION}`);
DB_MIGRATIONS.forEach((migration, index) => {
if (oldVersion <= index) migration(db);
});
@@ -520,7 +882,7 @@ function abortWithException(txn, e) {
txn._mx_abortexception = e;
try {
txn.abort();
} catch {
} catch (e) {
// sometimes we won't be able to abort the transaction
// (ie. if it's aborted or completed)
}
@@ -530,7 +892,6 @@ function promiseifyTxn(txn) {
txn.oncomplete = () => {
if (txn._mx_abortexception !== undefined) {
reject(txn._mx_abortexception);
return;
}
resolve(null);
};
@@ -538,7 +899,7 @@ function promiseifyTxn(txn) {
if (txn._mx_abortexception !== undefined) {
reject(txn._mx_abortexception);
} else {
logger.log("Error performing indexeddb txn", event);
_logger.logger.log("Error performing indexeddb txn", event);
reject(txn.error);
}
};
@@ -546,7 +907,7 @@ function promiseifyTxn(txn) {
if (txn._mx_abortexception !== undefined) {
reject(txn._mx_abortexception);
} else {
logger.log("Error performing indexeddb txn", event);
_logger.logger.log("Error performing indexeddb txn", event);
reject(txn.error);
}
};

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,13 @@
import { type Logger } from "../../logger.ts";
import { type CryptoStore, type ISession, type SessionExtended, type ISessionInfo, type IWithheld, MigrationState, type Mode, type SecretStorePrivateKeys, type InboundGroupSessionData, type IRoomEncryption } from "./base.ts";
import { type CrossSigningKeyInfo } from "../../crypto-api/index.ts";
import { PrefixedLogger } from "../../logger";
import { CryptoStore, IDeviceData, IProblem, ISession, ISessionInfo, IWithheld, Mode, OutgoingRoomKeyRequest, ParkedSharedHistory, SecretStorePrivateKeys } from "./base";
import { IRoomKeyRequestBody } from "../index";
import { ICrossSigningKey } from "../../client";
import { IOlmDevice } from "../algorithms/megolm";
import { IRoomEncryption } from "../RoomList";
import { InboundGroupSessionData } from "../OlmDevice";
/**
* Internal module. indexeddb storage for e2e.
*/
/**
* An implementation of CryptoStore, which is normally backed by an indexeddb,
* but with fallback to MemoryCryptoStore.
@@ -18,11 +25,6 @@ export declare class IndexedDBCryptoStore implements CryptoStore {
static STORE_ROOMS: string;
static STORE_BACKUP: string;
static exists(indexedDB: IDBFactory, dbName: string): Promise<boolean>;
/**
* Utility to check if a legacy crypto store exists and has not been migrated.
* Returns true if the store exists and has not been migrated, false otherwise.
*/
static existsAndIsNotMigrated(indexedDb: IDBFactory, dbName: string): Promise<boolean>;
private backendPromise?;
private backend?;
/**
@@ -32,14 +34,6 @@ export declare class IndexedDBCryptoStore implements CryptoStore {
* @param dbName - name of db to connect to
*/
constructor(indexedDB: IDBFactory, dbName: string);
/**
* Returns true if this CryptoStore has ever been initialised (ie, it might contain data).
*
* Implementation of {@link CryptoStore.containsData}.
*
* @internal
*/
containsData(): Promise<boolean>;
/**
* Ensure the database exists and is up-to-date, or fall back to
* a local storage or in-memory store.
@@ -57,21 +51,77 @@ export declare class IndexedDBCryptoStore implements CryptoStore {
*/
deleteAllData(): Promise<void>;
/**
* Get data on how much of the libolm to Rust Crypto migration has been done.
* Look for an existing outgoing room key request, and if none is found,
* add a new one
*
* Implementation of {@link CryptoStore.getMigrationState}.
*
* @internal
* @returns resolves to
* {@link OutgoingRoomKeyRequest}: either the
* same instance as passed in, or the existing one.
*/
getMigrationState(): Promise<MigrationState>;
getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise<OutgoingRoomKeyRequest>;
/**
* Set data on how much of the libolm to Rust Crypto migration has been done.
* Look for an existing room key request
*
* Implementation of {@link CryptoStore.setMigrationState}.
* @param requestBody - existing request to look for
*
* @internal
* @returns resolves to the matching
* {@link OutgoingRoomKeyRequest}, or null if
* not found
*/
setMigrationState(migrationState: MigrationState): Promise<void>;
getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<OutgoingRoomKeyRequest | null>;
/**
* Look for room key requests by state
*
* @param wantedStates - list of acceptable states
*
* @returns resolves to the a
* {@link OutgoingRoomKeyRequest}, or null if
* there are no pending requests in those states. If there are multiple
* requests in those states, an arbitrary one is chosen.
*/
getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise<OutgoingRoomKeyRequest | null>;
/**
* Look for room key requests by state –
* unlike above, return a list of all entries in one state.
*
* @returns Returns an array of requests in the given state
*/
getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise<OutgoingRoomKeyRequest[]>;
/**
* Look for room key requests by target device and state
*
* @param userId - Target user ID
* @param deviceId - Target device ID
* @param wantedStates - list of acceptable states
*
* @returns resolves to a list of all the
* {@link OutgoingRoomKeyRequest}
*/
getOutgoingRoomKeyRequestsByTarget(userId: string, deviceId: string, wantedStates: number[]): Promise<OutgoingRoomKeyRequest[]>;
/**
* Look for an existing room key request by id and state, and update it if
* found
*
* @param requestId - ID of request to update
* @param expectedState - state we expect to find the request in
* @param updates - name/value map of updates to apply
*
* @returns resolves to
* {@link OutgoingRoomKeyRequest}
* updated request, or null if no matching row was found
*/
updateOutgoingRoomKeyRequest(requestId: string, expectedState: number, updates: Partial<OutgoingRoomKeyRequest>): Promise<OutgoingRoomKeyRequest | null>;
/**
* Look for an existing room key request by id and state, and delete it if
* found
*
* @param requestId - ID of request to update
* @param expectedState - state we expect to find the request in
*
* @returns resolves once the operation is completed
*/
deleteOutgoingRoomKeyRequest(requestId: string, expectedState: number): Promise<OutgoingRoomKeyRequest | null>;
getAccount(txn: IDBTransaction, func: (accountPickle: string | null) => void): void;
/**
* Write the account pickle to the store.
@@ -89,13 +139,20 @@ export declare class IndexedDBCryptoStore implements CryptoStore {
* @param 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: IDBTransaction, func: (keys: Record<string, CrossSigningKeyInfo> | null) => void): void;
getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record<string, ICrossSigningKey> | null) => void): void;
/**
* @param txn - An active transaction. See doTxn().
* @param func - Called with the private key
* @param type - A key type
*/
getSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(txn: IDBTransaction, func: (key: SecretStorePrivateKeys[K] | null) => void, type: K): void;
/**
* Write the cross-signing keys back to the store
*
* @param txn - An active transaction. See doTxn().
* @param keys - keys object as getCrossSigningKeys()
*/
storeCrossSigningKeys(txn: IDBTransaction, keys: Record<string, ICrossSigningKey>): void;
/**
* Write the cross-signing private keys back to the store
*
@@ -137,6 +194,14 @@ export declare class IndexedDBCryptoStore implements CryptoStore {
getEndToEndSessions(deviceKey: string, txn: IDBTransaction, func: (sessions: {
[sessionId: string]: ISessionInfo;
}) => void): void;
/**
* Retrieve all end-to-end sessions
* @param txn - An active transaction. See doTxn().
* @param func - Called one for each session with
* an object with, deviceKey, lastReceivedMessageTs, sessionId
* and session keys.
*/
getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo | null) => void): void;
/**
* Store a session between the logged-in user and another device
* @param deviceKey - The public key of the other device.
@@ -145,33 +210,9 @@ export declare class IndexedDBCryptoStore implements CryptoStore {
* @param txn - An active transaction. See doTxn().
*/
storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: IDBTransaction): void;
/**
* Count the number of Megolm sessions in the database.
*
* Implementation of {@link CryptoStore.countEndToEndInboundGroupSessions}.
*
* @internal
*/
countEndToEndInboundGroupSessions(): Promise<number>;
/**
* Fetch a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndSessionsBatch}.
*
* @internal
*/
getEndToEndSessionsBatch(): Promise<null | ISessionInfo[]>;
/**
* Delete a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndSessionsBatch}.
*
* @internal
*/
deleteEndToEndSessionsBatch(sessions: {
deviceKey: string;
sessionId: string;
}[]): Promise<void>;
storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void>;
getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null>;
filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]>;
/**
* Retrieve the end-to-end inbound group session for a given
* server key and session ID
@@ -182,6 +223,24 @@ export declare class IndexedDBCryptoStore implements CryptoStore {
* to Base64 end-to-end session.
*/
getEndToEndInboundGroupSession(senderCurve25519Key: string, sessionId: string, txn: IDBTransaction, func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void): void;
/**
* Fetches all inbound group sessions in the store
* @param txn - An active transaction. See doTxn().
* @param func - Called once for each group session
* in the store with an object having keys `{senderKey, sessionId, sessionData}`,
* then once with null to indicate the end of the list.
*/
getAllEndToEndInboundGroupSessions(txn: IDBTransaction, func: (session: ISession | null) => void): void;
/**
* Adds an end-to-end inbound group session to the store.
* If there already exists an inbound group session with the same
* senderCurve25519Key and sessionID, the session will not be added.
* @param senderCurve25519Key - The sender's curve 25519 key
* @param sessionId - The ID of the session
* @param sessionData - The session data structure
* @param txn - An active transaction. See doTxn().
*/
addEndToEndInboundGroupSession(senderCurve25519Key: string, sessionId: string, sessionData: InboundGroupSessionData, txn: IDBTransaction): void;
/**
* Writes an end-to-end inbound group session to the store.
* If there already exists an inbound group session with the same
@@ -192,31 +251,58 @@ export declare class IndexedDBCryptoStore implements CryptoStore {
* @param txn - An active transaction. See doTxn().
*/
storeEndToEndInboundGroupSession(senderCurve25519Key: string, sessionId: string, sessionData: InboundGroupSessionData, txn: IDBTransaction): void;
storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key: string, sessionId: string, sessionData: IWithheld, txn: IDBTransaction): void;
/**
* Fetch a batch of Megolm sessions from the database.
* Store the state of all tracked devices
* This contains devices for each user, a tracking state for each user
* and a sync token matching the point in time the snapshot represents.
* These all need to be written out in full each time such that the snapshot
* is always consistent, so they are stored in one object.
*
* Implementation of {@link CryptoStore.getEndToEndInboundGroupSessionsBatch}.
*
* @internal
* @param txn - An active transaction. See doTxn().
*/
getEndToEndInboundGroupSessionsBatch(): Promise<SessionExtended[] | null>;
storeEndToEndDeviceData(deviceData: IDeviceData, txn: IDBTransaction): void;
/**
* Delete a batch of Megolm sessions from the database.
* Get the state of all tracked devices
*
* Implementation of {@link CryptoStore.deleteEndToEndInboundGroupSessionsBatch}.
*
* @internal
* @param txn - An active transaction. See doTxn().
* @param func - Function called with the
* device data
*/
deleteEndToEndInboundGroupSessionsBatch(sessions: {
senderKey: string;
sessionId: string;
}[]): Promise<void>;
getEndToEndDeviceData(txn: IDBTransaction, func: (deviceData: IDeviceData | null) => void): void;
/**
* Store the end-to-end state for a room.
* @param roomId - The room's ID.
* @param roomInfo - The end-to-end info for the room.
* @param txn - An active transaction. See doTxn().
*/
storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: IDBTransaction): void;
/**
* Get an object of `roomId->roomInfo` for all e2e rooms in the store
* @param txn - An active transaction. See doTxn().
* @param func - Function called with the end-to-end encrypted rooms
*/
getEndToEndRooms(txn: IDBTransaction, func: (rooms: Record<string, IRoomEncryption>) => void): void;
/**
* Get the inbound group sessions that need to be backed up.
* @param limit - The maximum number of sessions to retrieve. 0
* for no limit.
* @returns resolves to an array of inbound group sessions
*/
getSessionsNeedingBackup(limit: number): Promise<ISession[]>;
/**
* Count the inbound group sessions that need to be backed up.
* @param txn - An active transaction. See doTxn(). (optional)
* @returns resolves to the number of sessions
*/
countSessionsNeedingBackup(txn?: IDBTransaction): Promise<number>;
/**
* Unmark sessions as needing to be backed up.
* @param sessions - The sessions that need to be backed up.
* @param txn - An active transaction. See doTxn(). (optional)
* @returns resolves when the sessions are unmarked
*/
unmarkSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void>;
/**
* Mark sessions as needing to be backed up.
* @param sessions - The sessions that need to be backed up.
@@ -224,6 +310,29 @@ export declare class IndexedDBCryptoStore implements CryptoStore {
* @returns resolves when the sessions are marked
*/
markSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void>;
/**
* Add a shared-history group session for a room.
* @param roomId - The room that the key belongs to
* @param senderKey - The sender's curve 25519 key
* @param sessionId - The ID of the session
* @param txn - An active transaction. See doTxn(). (optional)
*/
addSharedHistoryInboundGroupSession(roomId: string, senderKey: string, sessionId: string, txn?: IDBTransaction): void;
/**
* Get the shared-history group session for a room.
* @param roomId - The room that the key belongs to
* @param txn - An active transaction. See doTxn(). (optional)
* @returns Promise which resolves to an array of [senderKey, sessionId]
*/
getSharedHistoryInboundGroupSessions(roomId: string, txn?: IDBTransaction): Promise<[senderKey: string, sessionId: string][]>;
/**
* Park a shared-history group session for a room we may be invited to later.
*/
addParkedSharedHistory(roomId: string, parkedData: ParkedSharedHistory, txn?: IDBTransaction): void;
/**
* Pop out all shared-history group sessions for a room.
*/
takeParkedSharedHistory(roomId: string, txn?: IDBTransaction): Promise<ParkedSharedHistory[]>;
/**
* Perform a transaction on the crypto store. Any store methods
* that require a transaction (txn) object to be passed in may
@@ -246,6 +355,6 @@ export declare class IndexedDBCryptoStore implements CryptoStore {
* reject with that exception. On synchronous backends, the
* exception will propagate to the caller of the getFoo method.
*/
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: IDBTransaction) => T, log?: Logger): Promise<T>;
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: IDBTransaction) => T, log?: PrefixedLogger): Promise<T>;
}
//# sourceMappingURL=indexeddb-crypto-store.d.ts.map

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,19 @@
import _defineProperty from "@babel/runtime/helpers/defineProperty";
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.IndexedDBCryptoStore = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _logger = require("../../logger");
var _localStorageCryptoStore = require("./localStorage-crypto-store");
var _memoryCryptoStore = require("./memory-crypto-store");
var IndexedDBCryptoStoreBackend = _interopRequireWildcard(require("./indexeddb-crypto-store-backend"));
var _errors = require("../../errors");
var IndexedDBHelpers = _interopRequireWildcard(require("../../indexeddb-helpers"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/*
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
@@ -15,14 +30,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger } from "../../logger.js";
import { LocalStorageCryptoStore } from "./localStorage-crypto-store.js";
import { MemoryCryptoStore } from "./memory-crypto-store.js";
import * as IndexedDBCryptoStoreBackend from "./indexeddb-crypto-store-backend.js";
import { InvalidCryptoStoreError, InvalidCryptoStoreState } from "../../errors.js";
import * as IndexedDBHelpers from "../../indexeddb-helpers.js";
import { MigrationState, ACCOUNT_OBJECT_KEY_MIGRATION_STATE } from "./base.js";
/*
/**
* Internal module. indexeddb storage for e2e.
*/
@@ -30,53 +38,10 @@ import { MigrationState, ACCOUNT_OBJECT_KEY_MIGRATION_STATE } from "./base.js";
* An implementation of CryptoStore, which is normally backed by an indexeddb,
* but with fallback to MemoryCryptoStore.
*/
export class IndexedDBCryptoStore {
class IndexedDBCryptoStore {
static exists(indexedDB, dbName) {
return IndexedDBHelpers.exists(indexedDB, dbName);
}
/**
* Utility to check if a legacy crypto store exists and has not been migrated.
* Returns true if the store exists and has not been migrated, false otherwise.
*/
static existsAndIsNotMigrated(indexedDb, dbName) {
return new Promise((resolve, reject) => {
let exists = true;
const openDBRequest = indexedDb.open(dbName);
openDBRequest.onupgradeneeded = () => {
// Since we did not provide an explicit version when opening, this event
// should only fire if the DB did not exist before at any version.
exists = false;
};
openDBRequest.onblocked = () => reject(openDBRequest.error);
openDBRequest.onsuccess = () => {
const db = openDBRequest.result;
if (!exists) {
db.close();
// The DB did not exist before, but has been created as part of this
// existence check. Delete it now to restore previous state. Delete can
// actually take a while to complete in some browsers, so don't wait for
// it. This won't block future open calls that a store might issue next to
// properly set up the DB.
indexedDb.deleteDatabase(dbName);
resolve(false);
} else {
const tx = db.transaction([IndexedDBCryptoStore.STORE_ACCOUNT], "readonly");
const objectStore = tx.objectStore(IndexedDBCryptoStore.STORE_ACCOUNT);
const getReq = objectStore.get(ACCOUNT_OBJECT_KEY_MIGRATION_STATE);
getReq.onsuccess = () => {
const migrationState = getReq.result ?? MigrationState.NOT_STARTED;
resolve(migrationState === MigrationState.NOT_STARTED);
};
getReq.onerror = () => {
reject(getReq.error);
};
db.close();
}
};
openDBRequest.onerror = () => reject(openDBRequest.error);
});
}
/**
* Create a new IndexedDBCryptoStore
*
@@ -84,21 +49,10 @@ export class IndexedDBCryptoStore {
* @param dbName - name of db to connect to
*/
constructor(indexedDB, dbName) {
_defineProperty(this, "backendPromise", void 0);
_defineProperty(this, "backend", void 0);
this.indexedDB = indexedDB;
this.dbName = dbName;
}
/**
* Returns true if this CryptoStore has ever been initialised (ie, it might contain data).
*
* Implementation of {@link CryptoStore.containsData}.
*
* @internal
*/
async containsData() {
return IndexedDBCryptoStore.exists(this.indexedDB, this.dbName);
(0, _defineProperty2.default)(this, "backendPromise", void 0);
(0, _defineProperty2.default)(this, "backend", void 0);
}
/**
@@ -119,7 +73,7 @@ export class IndexedDBCryptoStore {
reject(new Error("no indexeddb support available"));
return;
}
logger.log(`connecting to indexeddb ${this.dbName}`);
_logger.logger.log(`connecting to indexeddb ${this.dbName}`);
const req = this.indexedDB.open(this.dbName, IndexedDBCryptoStoreBackend.VERSION);
req.onupgradeneeded = ev => {
const db = req.result;
@@ -127,15 +81,15 @@ export class IndexedDBCryptoStore {
IndexedDBCryptoStoreBackend.upgradeDatabase(db, oldVersion);
};
req.onblocked = () => {
logger.log(`can't yet open IndexedDBCryptoStore because it is open elsewhere`);
_logger.logger.log(`can't yet open IndexedDBCryptoStore because it is open elsewhere`);
};
req.onerror = ev => {
logger.log("Error connecting to indexeddb", ev);
_logger.logger.log("Error connecting to indexeddb", ev);
reject(req.error);
};
req.onsuccess = () => {
const db = req.result;
logger.log(`connected to indexeddb ${this.dbName}`);
_logger.logger.log(`connected to indexeddb ${this.dbName}`);
resolve(new IndexedDBCryptoStoreBackend.Backend(db));
};
}).then(backend => {
@@ -147,20 +101,17 @@ export class IndexedDBCryptoStore {
}).then(() => backend);
}).catch(e => {
if (e.name === "VersionError") {
logger.warn("Crypto DB is too new for us to use!", e);
_logger.logger.warn("Crypto DB is too new for us to use!", e);
// don't fall back to a different store: the user has crypto data
// in this db so we should use it or nothing at all.
throw new InvalidCryptoStoreError(InvalidCryptoStoreState.TooNew);
throw new _errors.InvalidCryptoStoreError(_errors.InvalidCryptoStoreState.TooNew);
}
logger.warn(`unable to connect to indexeddb ${this.dbName}: falling back to localStorage store: ${e}`);
_logger.logger.warn(`unable to connect to indexeddb ${this.dbName}` + `: falling back to localStorage store: ${e}`);
try {
if (!(globalThis.localStorage instanceof Storage)) {
throw new Error("localStorage is not available");
}
return new LocalStorageCryptoStore(globalThis.localStorage);
return new _localStorageCryptoStore.LocalStorageCryptoStore(global.localStorage);
} catch (e) {
logger.warn(`Unable to open localStorage: falling back to in-memory store: ${e}`);
return new MemoryCryptoStore();
_logger.logger.warn(`unable to open localStorage: falling back to in-memory store: ${e}`);
return new _memoryCryptoStore.MemoryCryptoStore();
}
}).then(backend => {
this.backend = backend;
@@ -180,47 +131,118 @@ export class IndexedDBCryptoStore {
reject(new Error("no indexeddb support available"));
return;
}
logger.log(`Removing indexeddb instance: ${this.dbName}`);
_logger.logger.log(`Removing indexeddb instance: ${this.dbName}`);
const req = this.indexedDB.deleteDatabase(this.dbName);
req.onblocked = () => {
logger.log(`can't yet delete IndexedDBCryptoStore because it is open elsewhere`);
_logger.logger.log(`can't yet delete IndexedDBCryptoStore because it is open elsewhere`);
};
req.onerror = ev => {
logger.log("Error deleting data from indexeddb", ev);
_logger.logger.log("Error deleting data from indexeddb", ev);
reject(req.error);
};
req.onsuccess = () => {
logger.log(`Removed indexeddb instance: ${this.dbName}`);
_logger.logger.log(`Removed indexeddb instance: ${this.dbName}`);
resolve();
};
}).catch(e => {
// in firefox, with indexedDB disabled, this fails with a
// DOMError. We treat this as non-fatal, so that people can
// still use the app.
logger.warn(`unable to delete IndexedDBCryptoStore: ${e}`);
_logger.logger.warn(`unable to delete IndexedDBCryptoStore: ${e}`);
});
}
/**
* Get data on how much of the libolm to Rust Crypto migration has been done.
* Look for an existing outgoing room key request, and if none is found,
* add a new one
*
* Implementation of {@link CryptoStore.getMigrationState}.
*
* @internal
* @returns resolves to
* {@link OutgoingRoomKeyRequest}: either the
* same instance as passed in, or the existing one.
*/
getMigrationState() {
return this.backend.getMigrationState();
getOrAddOutgoingRoomKeyRequest(request) {
return this.backend.getOrAddOutgoingRoomKeyRequest(request);
}
/**
* Set data on how much of the libolm to Rust Crypto migration has been done.
* Look for an existing room key request
*
* Implementation of {@link CryptoStore.setMigrationState}.
* @param requestBody - existing request to look for
*
* @internal
* @returns resolves to the matching
* {@link OutgoingRoomKeyRequest}, or null if
* not found
*/
setMigrationState(migrationState) {
return this.backend.setMigrationState(migrationState);
getOutgoingRoomKeyRequest(requestBody) {
return this.backend.getOutgoingRoomKeyRequest(requestBody);
}
/**
* Look for room key requests by state
*
* @param wantedStates - list of acceptable states
*
* @returns resolves to the a
* {@link OutgoingRoomKeyRequest}, or null if
* 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);
}
/**
* Look for room key requests by state –
* unlike above, return a list of all entries in one state.
*
* @returns Returns an array of requests in the given state
*/
getAllOutgoingRoomKeyRequestsByState(wantedState) {
return this.backend.getAllOutgoingRoomKeyRequestsByState(wantedState);
}
/**
* Look for room key requests by target device and state
*
* @param userId - Target user ID
* @param deviceId - Target device ID
* @param wantedStates - list of acceptable states
*
* @returns resolves to a list of all the
* {@link OutgoingRoomKeyRequest}
*/
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
return this.backend.getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates);
}
/**
* Look for an existing room key request by id and state, and update it if
* found
*
* @param requestId - ID of request to update
* @param expectedState - state we expect to find the request in
* @param updates - name/value map of updates to apply
*
* @returns resolves to
* {@link OutgoingRoomKeyRequest}
* updated request, or null if no matching row was found
*/
updateOutgoingRoomKeyRequest(requestId, expectedState, updates) {
return this.backend.updateOutgoingRoomKeyRequest(requestId, expectedState, updates);
}
/**
* Look for an existing room key request by id and state, and delete it if
* found
*
* @param requestId - ID of request to update
* @param expectedState - state we expect to find the request in
*
* @returns resolves once the operation is completed
*/
deleteOutgoingRoomKeyRequest(requestId, expectedState) {
return this.backend.deleteOutgoingRoomKeyRequest(requestId, expectedState);
}
// Olm Account
@@ -268,6 +290,16 @@ export class IndexedDBCryptoStore {
this.backend.getSecretStorePrivateKey(txn, func, type);
}
/**
* Write the cross-signing keys back to the store
*
* @param txn - An active transaction. See doTxn().
* @param keys - keys object as getCrossSigningKeys()
*/
storeCrossSigningKeys(txn, keys) {
this.backend.storeCrossSigningKeys(txn, keys);
}
/**
* Write the cross-signing private keys back to the store
*
@@ -321,6 +353,17 @@ export class IndexedDBCryptoStore {
this.backend.getEndToEndSessions(deviceKey, txn, func);
}
/**
* Retrieve all end-to-end sessions
* @param txn - An active transaction. See doTxn().
* @param func - Called one for each session with
* an object with, deviceKey, lastReceivedMessageTs, sessionId
* and session keys.
*/
getAllEndToEndSessions(txn, func) {
this.backend.getAllEndToEndSessions(txn, func);
}
/**
* Store a session between the logged-in user and another device
* @param deviceKey - The public key of the other device.
@@ -331,38 +374,14 @@ export class IndexedDBCryptoStore {
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
this.backend.storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn);
}
/**
* Count the number of Megolm sessions in the database.
*
* Implementation of {@link CryptoStore.countEndToEndInboundGroupSessions}.
*
* @internal
*/
countEndToEndInboundGroupSessions() {
return this.backend.countEndToEndInboundGroupSessions();
storeEndToEndSessionProblem(deviceKey, type, fixed) {
return this.backend.storeEndToEndSessionProblem(deviceKey, type, fixed);
}
/**
* Fetch a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndSessionsBatch}.
*
* @internal
*/
getEndToEndSessionsBatch() {
return this.backend.getEndToEndSessionsBatch();
getEndToEndSessionProblem(deviceKey, timestamp) {
return this.backend.getEndToEndSessionProblem(deviceKey, timestamp);
}
/**
* Delete a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndSessionsBatch}.
*
* @internal
*/
deleteEndToEndSessionsBatch(sessions) {
return this.backend.deleteEndToEndSessionsBatch(sessions);
filterOutNotifiedErrorDevices(devices) {
return this.backend.filterOutNotifiedErrorDevices(devices);
}
// Inbound group sessions
@@ -380,6 +399,30 @@ export class IndexedDBCryptoStore {
this.backend.getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func);
}
/**
* Fetches all inbound group sessions in the store
* @param txn - An active transaction. See doTxn().
* @param func - Called once for each group session
* 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);
}
/**
* Adds an end-to-end inbound group session to the store.
* If there already exists an inbound group session with the same
* senderCurve25519Key and sessionID, the session will not be added.
* @param senderCurve25519Key - The sender's curve 25519 key
* @param sessionId - The ID of the session
* @param sessionData - The session data structure
* @param txn - An active transaction. See doTxn().
*/
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
this.backend.addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
}
/**
* Writes an end-to-end inbound group session to the store.
* If there already exists an inbound group session with the same
@@ -392,27 +435,46 @@ export class IndexedDBCryptoStore {
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
this.backend.storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
}
storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn) {
this.backend.storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn);
}
// End-to-end device tracking
/**
* Fetch a batch of Megolm sessions from the database.
* Store the state of all tracked devices
* This contains devices for each user, a tracking state for each user
* and a sync token matching the point in time the snapshot represents.
* These all need to be written out in full each time such that the snapshot
* is always consistent, so they are stored in one object.
*
* Implementation of {@link CryptoStore.getEndToEndInboundGroupSessionsBatch}.
*
* @internal
* @param txn - An active transaction. See doTxn().
*/
getEndToEndInboundGroupSessionsBatch() {
return this.backend.getEndToEndInboundGroupSessionsBatch();
storeEndToEndDeviceData(deviceData, txn) {
this.backend.storeEndToEndDeviceData(deviceData, txn);
}
/**
* Delete a batch of Megolm sessions from the database.
* Get the state of all tracked devices
*
* Implementation of {@link CryptoStore.deleteEndToEndInboundGroupSessionsBatch}.
*
* @internal
* @param txn - An active transaction. See doTxn().
* @param func - Function called with the
* device data
*/
deleteEndToEndInboundGroupSessionsBatch(sessions) {
return this.backend.deleteEndToEndInboundGroupSessionsBatch(sessions);
getEndToEndDeviceData(txn, func) {
this.backend.getEndToEndDeviceData(txn, func);
}
// End to End Rooms
/**
* Store the end-to-end state for a room.
* @param roomId - The room's ID.
* @param 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);
}
/**
@@ -424,6 +486,37 @@ export class IndexedDBCryptoStore {
this.backend.getEndToEndRooms(txn, func);
}
// session backups
/**
* Get the inbound group sessions that need to be backed up.
* @param limit - The maximum number of sessions to retrieve. 0
* for no limit.
* @returns resolves to an array of inbound group sessions
*/
getSessionsNeedingBackup(limit) {
return this.backend.getSessionsNeedingBackup(limit);
}
/**
* Count the inbound group sessions that need to be backed up.
* @param txn - An active transaction. See doTxn(). (optional)
* @returns resolves to the number of sessions
*/
countSessionsNeedingBackup(txn) {
return this.backend.countSessionsNeedingBackup(txn);
}
/**
* Unmark sessions as needing to be backed up.
* @param sessions - The sessions that need to be backed up.
* @param txn - An active transaction. See doTxn(). (optional)
* @returns resolves when the sessions are unmarked
*/
unmarkSessionsNeedingBackup(sessions, txn) {
return this.backend.unmarkSessionsNeedingBackup(sessions, txn);
}
/**
* Mark sessions as needing to be backed up.
* @param sessions - The sessions that need to be backed up.
@@ -434,6 +527,41 @@ export class IndexedDBCryptoStore {
return this.backend.markSessionsNeedingBackup(sessions, txn);
}
/**
* Add a shared-history group session for a room.
* @param roomId - The room that the key belongs to
* @param senderKey - The sender's curve 25519 key
* @param 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);
}
/**
* Get the shared-history group session for a room.
* @param roomId - The room that the key belongs to
* @param txn - An active transaction. See doTxn(). (optional)
* @returns Promise which resolves to an array of [senderKey, sessionId]
*/
getSharedHistoryInboundGroupSessions(roomId, txn) {
return this.backend.getSharedHistoryInboundGroupSessions(roomId, txn);
}
/**
* Park a shared-history group session for a room we may be invited to later.
*/
addParkedSharedHistory(roomId, parkedData, txn) {
this.backend.addParkedSharedHistory(roomId, parkedData, txn);
}
/**
* Pop out all shared-history group sessions for a room.
*/
takeParkedSharedHistory(roomId, txn) {
return this.backend.takeParkedSharedHistory(roomId, txn);
}
/**
* Perform a transaction on the crypto store. Any store methods
* that require a transaction (txn) object to be passed in may
@@ -460,13 +588,14 @@ export class IndexedDBCryptoStore {
return this.backend.doTxn(mode, stores, func, log);
}
}
_defineProperty(IndexedDBCryptoStore, "STORE_ACCOUNT", "account");
_defineProperty(IndexedDBCryptoStore, "STORE_SESSIONS", "sessions");
_defineProperty(IndexedDBCryptoStore, "STORE_INBOUND_GROUP_SESSIONS", "inbound_group_sessions");
_defineProperty(IndexedDBCryptoStore, "STORE_INBOUND_GROUP_SESSIONS_WITHHELD", "inbound_group_sessions_withheld");
_defineProperty(IndexedDBCryptoStore, "STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS", "shared_history_inbound_group_sessions");
_defineProperty(IndexedDBCryptoStore, "STORE_PARKED_SHARED_HISTORY", "parked_shared_history");
_defineProperty(IndexedDBCryptoStore, "STORE_DEVICE_DATA", "device_data");
_defineProperty(IndexedDBCryptoStore, "STORE_ROOMS", "rooms");
_defineProperty(IndexedDBCryptoStore, "STORE_BACKUP", "sessions_needing_backup");
exports.IndexedDBCryptoStore = IndexedDBCryptoStore;
(0, _defineProperty2.default)(IndexedDBCryptoStore, "STORE_ACCOUNT", "account");
(0, _defineProperty2.default)(IndexedDBCryptoStore, "STORE_SESSIONS", "sessions");
(0, _defineProperty2.default)(IndexedDBCryptoStore, "STORE_INBOUND_GROUP_SESSIONS", "inbound_group_sessions");
(0, _defineProperty2.default)(IndexedDBCryptoStore, "STORE_INBOUND_GROUP_SESSIONS_WITHHELD", "inbound_group_sessions_withheld");
(0, _defineProperty2.default)(IndexedDBCryptoStore, "STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS", "shared_history_inbound_group_sessions");
(0, _defineProperty2.default)(IndexedDBCryptoStore, "STORE_PARKED_SHARED_HISTORY", "parked_shared_history");
(0, _defineProperty2.default)(IndexedDBCryptoStore, "STORE_DEVICE_DATA", "device_data");
(0, _defineProperty2.default)(IndexedDBCryptoStore, "STORE_ROOMS", "rooms");
(0, _defineProperty2.default)(IndexedDBCryptoStore, "STORE_BACKUP", "sessions_needing_backup");
//# sourceMappingURL=indexeddb-crypto-store.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,90 +1,36 @@
import { MemoryCryptoStore } from "./memory-crypto-store.ts";
import { type CryptoStore, type ISession, type SessionExtended, type ISessionInfo, type IWithheld, MigrationState, type Mode, type SecretStorePrivateKeys, type InboundGroupSessionData, type IRoomEncryption } from "./base.ts";
import { type CrossSigningKeyInfo } from "../../crypto-api/index.ts";
export declare class LocalStorageCryptoStore extends MemoryCryptoStore implements CryptoStore {
import { MemoryCryptoStore } from "./memory-crypto-store";
import { IDeviceData, IProblem, ISession, ISessionInfo, IWithheld, Mode, SecretStorePrivateKeys } from "./base";
import { IOlmDevice } from "../algorithms/megolm";
import { IRoomEncryption } from "../RoomList";
import { ICrossSigningKey } from "../../client";
import { InboundGroupSessionData } from "../OlmDevice";
export declare class LocalStorageCryptoStore extends MemoryCryptoStore {
private readonly store;
static exists(store: Storage): boolean;
constructor(store: Storage);
/**
* Returns true if this CryptoStore has ever been initialised (ie, it might contain data).
*
* Implementation of {@link CryptoStore.containsData}.
*
* @internal
*/
containsData(): Promise<boolean>;
/**
* Get data on how much of the libolm to Rust Crypto migration has been done.
*
* Implementation of {@link CryptoStore.getMigrationState}.
*
* @internal
*/
getMigrationState(): Promise<MigrationState>;
/**
* Set data on how much of the libolm to Rust Crypto migration has been done.
*
* Implementation of {@link CryptoStore.setMigrationState}.
*
* @internal
*/
setMigrationState(migrationState: MigrationState): Promise<void>;
countEndToEndSessions(txn: unknown, func: (count: number) => void): void;
private _getEndToEndSessions;
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;
/**
* Fetch a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndSessionsBatch}.
*
* @internal
*/
getEndToEndSessionsBatch(): Promise<null | ISessionInfo[]>;
/**
* Delete a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndSessionsBatch}.
*
* @internal
*/
deleteEndToEndSessionsBatch(sessions: {
deviceKey: string;
sessionId: string;
}[]): Promise<void>;
storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void>;
getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null>;
filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]>;
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;
/**
* Count the number of Megolm sessions in the database.
*
* Implementation of {@link CryptoStore.countEndToEndInboundGroupSessions}.
*
* @internal
*/
countEndToEndInboundGroupSessions(): Promise<number>;
/**
* Fetch a batch of Megolm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndInboundGroupSessionsBatch}.
*
* @internal
*/
getEndToEndInboundGroupSessionsBatch(): Promise<SessionExtended[] | null>;
/**
* Delete a batch of Megolm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndInboundGroupSessionsBatch}.
*
* @internal
*/
deleteEndToEndInboundGroupSessionsBatch(sessions: {
senderKey: string;
sessionId: string;
}[]): Promise<void>;
storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key: string, sessionId: string, sessionData: IWithheld, txn: unknown): void;
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(): Promise<number>;
unmarkSessionsNeedingBackup(sessions: ISession[]): Promise<void>;
markSessionsNeedingBackup(sessions: ISession[]): Promise<void>;
/**
* Delete all data from this store.
@@ -94,8 +40,9 @@ export declare class LocalStorageCryptoStore extends MemoryCryptoStore implement
deleteAllData(): Promise<void>;
getAccount(txn: unknown, func: (accountPickle: string | null) => void): void;
storeAccount(txn: unknown, accountPickle: string): void;
getCrossSigningKeys(txn: unknown, func: (keys: Record<string, CrossSigningKeyInfo> | null) => void): void;
getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey> | null) => void): void;
getSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(txn: unknown, func: (key: SecretStorePrivateKeys[K] | null) => void, type: K): void;
storeCrossSigningKeys(txn: unknown, keys: Record<string, ICrossSigningKey>): void;
storeSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(txn: unknown, type: K, key: SecretStorePrivateKeys[K]): void;
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: unknown) => T): Promise<T>;
}

View File

@@ -1 +1 @@
{"version":3,"file":"localStorage-crypto-store.d.ts","sourceRoot":"","sources":["../../../src/crypto/store/localStorage-crypto-store.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"localStorage-crypto-store.d.ts","sourceRoot":"","sources":["../../../src/crypto/store/localStorage-crypto-store.ts"],"names":[],"mappings":"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"}

View File

@@ -1,3 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.LocalStorageCryptoStore = void 0;
var _logger = require("../../logger");
var _memoryCryptoStore = require("./memory-crypto-store");
var _utils = require("../../utils");
/*
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
@@ -14,9 +23,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger } from "../../logger.js";
import { MemoryCryptoStore } from "./memory-crypto-store.js";
import { MigrationState, SESSION_BATCH_SIZE } from "./base.js";
/**
* Internal module. Partial localStorage backed storage for e2e.
* This is not a full crypto store, just the in-memory store with
@@ -26,9 +32,10 @@ import { MigrationState, SESSION_BATCH_SIZE } from "./base.js";
*/
const E2E_PREFIX = "crypto.";
const KEY_END_TO_END_MIGRATION_STATE = E2E_PREFIX + "migration";
const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account";
const KEY_CROSS_SIGNING_KEYS = E2E_PREFIX + "cross_signing_keys";
const KEY_NOTIFIED_ERROR_DEVICES = E2E_PREFIX + "notified_error_devices";
const KEY_DEVICE_DATA = E2E_PREFIX + "device_data";
const KEY_INBOUND_SESSION_PREFIX = E2E_PREFIX + "inboundgroupsessions/";
const KEY_INBOUND_SESSION_WITHHELD_PREFIX = E2E_PREFIX + "inboundgroupsessions.withheld/";
const KEY_ROOMS_PREFIX = E2E_PREFIX + "rooms/";
@@ -36,6 +43,9 @@ const KEY_SESSIONS_NEEDING_BACKUP = E2E_PREFIX + "sessionsneedingbackup";
function keyEndToEndSessions(deviceKey) {
return E2E_PREFIX + "sessions/" + deviceKey;
}
function keyEndToEndSessionProblems(deviceKey) {
return E2E_PREFIX + "session.problems/" + deviceKey;
}
function keyEndToEndInboundGroupSession(senderKey, sessionId) {
return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId;
}
@@ -45,11 +55,12 @@ function keyEndToEndInboundGroupSessionWithheld(senderKey, sessionId) {
function keyEndToEndRoomsPrefix(roomId) {
return KEY_ROOMS_PREFIX + roomId;
}
export class LocalStorageCryptoStore extends MemoryCryptoStore {
class LocalStorageCryptoStore extends _memoryCryptoStore.MemoryCryptoStore {
static exists(store) {
const length = store.length;
for (let i = 0; i < length; i++) {
if (store.key(i)?.startsWith(E2E_PREFIX)) {
var _store$key;
if ((_store$key = store.key(i)) !== null && _store$key !== void 0 && _store$key.startsWith(E2E_PREFIX)) {
return true;
}
}
@@ -60,52 +71,18 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
this.store = store;
}
/**
* Returns true if this CryptoStore has ever been initialised (ie, it might contain data).
*
* Implementation of {@link CryptoStore.containsData}.
*
* @internal
*/
async containsData() {
return LocalStorageCryptoStore.exists(this.store);
}
/**
* Get data on how much of the libolm to Rust Crypto migration has been done.
*
* Implementation of {@link CryptoStore.getMigrationState}.
*
* @internal
*/
async getMigrationState() {
return getJsonItem(this.store, KEY_END_TO_END_MIGRATION_STATE) ?? MigrationState.NOT_STARTED;
}
/**
* Set data on how much of the libolm to Rust Crypto migration has been done.
*
* Implementation of {@link CryptoStore.setMigrationState}.
*
* @internal
*/
async setMigrationState(migrationState) {
setJsonItem(this.store, KEY_END_TO_END_MIGRATION_STATE, migrationState);
}
// Olm Sessions
countEndToEndSessions(txn, func) {
let count = 0;
for (let i = 0; i < this.store.length; ++i) {
const key = this.store.key(i);
if (key?.startsWith(keyEndToEndSessions(""))) {
const sessions = getJsonItem(this.store, key);
count += Object.keys(sessions ?? {}).length;
}
var _this$store$key;
if ((_this$store$key = this.store.key(i)) !== null && _this$store$key !== void 0 && _this$store$key.startsWith(keyEndToEndSessions(""))) ++count;
}
func(count);
}
// eslint-disable-next-line @typescript-eslint/naming-convention
_getEndToEndSessions(deviceKey) {
const sessions = getJsonItem(this.store, keyEndToEndSessions(deviceKey));
const fixedSessions = {};
@@ -124,67 +101,82 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
}
getEndToEndSession(deviceKey, sessionId, txn, func) {
const sessions = this._getEndToEndSessions(deviceKey);
func(sessions[sessionId] ?? {});
func(sessions[sessionId] || {});
}
getEndToEndSessions(deviceKey, txn, func) {
func(this._getEndToEndSessions(deviceKey) ?? {});
func(this._getEndToEndSessions(deviceKey) || {});
}
getAllEndToEndSessions(txn, func) {
for (let i = 0; i < this.store.length; ++i) {
var _this$store$key2;
if ((_this$store$key2 = this.store.key(i)) !== null && _this$store$key2 !== void 0 && _this$store$key2.startsWith(keyEndToEndSessions(""))) {
const deviceKey = this.store.key(i).split("/")[1];
for (const sess of Object.values(this._getEndToEndSessions(deviceKey))) {
func(sess);
}
}
}
}
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
const sessions = this._getEndToEndSessions(deviceKey) || {};
sessions[sessionId] = sessionInfo;
setJsonItem(this.store, keyEndToEndSessions(deviceKey), sessions);
}
/**
* Fetch a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndSessionsBatch}.
*
* @internal
*/
async getEndToEndSessionsBatch() {
const result = [];
for (let i = 0; i < this.store.length; ++i) {
if (this.store.key(i)?.startsWith(keyEndToEndSessions(""))) {
const deviceKey = this.store.key(i).split("/")[1];
for (const session of Object.values(this._getEndToEndSessions(deviceKey))) {
result.push(session);
if (result.length >= SESSION_BATCH_SIZE) {
return result;
}
}
}
}
if (result.length === 0) {
// No sessions left.
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
const key = keyEndToEndSessionProblems(deviceKey);
const problems = getJsonItem(this.store, key) || [];
problems.push({
type,
fixed,
time: Date.now()
});
problems.sort((a, b) => {
return a.time - b.time;
});
setJsonItem(this.store, key, problems);
}
async getEndToEndSessionProblem(deviceKey, timestamp) {
const key = keyEndToEndSessionProblems(deviceKey);
const problems = getJsonItem(this.store, key) || [];
if (!problems.length) {
return null;
}
// There are fewer sessions than the batch size; return the final batch of sessions.
return result;
}
/**
* Delete a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndSessionsBatch}.
*
* @internal
*/
async deleteEndToEndSessionsBatch(sessions) {
for (const {
deviceKey,
sessionId
} of sessions) {
const deviceSessions = this._getEndToEndSessions(deviceKey) || {};
delete deviceSessions[sessionId];
if (Object.keys(deviceSessions).length === 0) {
// No more sessions for this device.
this.store.removeItem(keyEndToEndSessions(deviceKey));
} else {
setJsonItem(this.store, keyEndToEndSessions(deviceKey), deviceSessions);
const lastProblem = problems[problems.length - 1];
for (const problem of problems) {
if (problem.time > timestamp) {
return Object.assign({}, problem, {
fixed: lastProblem.fixed
});
}
}
if (lastProblem.fixed) {
return null;
} else {
return lastProblem;
}
}
async filterOutNotifiedErrorDevices(devices) {
const notifiedErrorDevices = getJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES) || {};
const ret = [];
for (const device of devices) {
const {
userId,
deviceInfo
} = device;
if (userId in notifiedErrorDevices) {
if (!(deviceInfo.deviceId in notifiedErrorDevices[userId])) {
ret.push(device);
(0, _utils.safeSet)(notifiedErrorDevices[userId], deviceInfo.deviceId, true);
}
} else {
ret.push(device);
(0, _utils.safeSet)(notifiedErrorDevices, userId, {
[deviceInfo.deviceId]: true
});
}
}
setJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES, notifiedErrorDevices);
return ret;
}
// Inbound Group Sessions
@@ -192,96 +184,91 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
func(getJsonItem(this.store, keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId)), getJsonItem(this.store, keyEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId)));
}
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
setJsonItem(this.store, keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId), sessionData);
}
/**
* Count the number of Megolm sessions in the database.
*
* Implementation of {@link CryptoStore.countEndToEndInboundGroupSessions}.
*
* @internal
*/
async countEndToEndInboundGroupSessions() {
let count = 0;
getAllEndToEndInboundGroupSessions(txn, func) {
for (let i = 0; i < this.store.length; ++i) {
const key = this.store.key(i);
if (key?.startsWith(KEY_INBOUND_SESSION_PREFIX)) {
count += 1;
}
}
return count;
}
/**
* Fetch a batch of Megolm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndInboundGroupSessionsBatch}.
*
* @internal
*/
async getEndToEndInboundGroupSessionsBatch() {
const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
const result = [];
for (let i = 0; i < this.store.length; ++i) {
const key = this.store.key(i);
if (key?.startsWith(KEY_INBOUND_SESSION_PREFIX)) {
const key2 = key.slice(KEY_INBOUND_SESSION_PREFIX.length);
if (key !== null && key !== void 0 && key.startsWith(KEY_INBOUND_SESSION_PREFIX)) {
// 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
// (hence 43 characters long).
result.push({
senderKey: key2.slice(0, 43),
sessionId: key2.slice(44),
sessionData: getJsonItem(this.store, key),
needsBackup: key2 in sessionsNeedingBackup
func({
senderKey: key.slice(KEY_INBOUND_SESSION_PREFIX.length, KEY_INBOUND_SESSION_PREFIX.length + 43),
sessionId: key.slice(KEY_INBOUND_SESSION_PREFIX.length + 44),
sessionData: getJsonItem(this.store, key)
});
if (result.length >= SESSION_BATCH_SIZE) {
return result;
}
}
}
if (result.length === 0) {
// No sessions left.
return null;
}
// There are fewer sessions than the batch size; return the final batch of sessions.
return result;
func(null);
}
/**
* Delete a batch of Megolm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndInboundGroupSessionsBatch}.
*
* @internal
*/
async deleteEndToEndInboundGroupSessionsBatch(sessions) {
for (const {
senderKey,
sessionId
} of sessions) {
const k = keyEndToEndInboundGroupSession(senderKey, sessionId);
this.store.removeItem(k);
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
const existing = getJsonItem(this.store, keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId));
if (!existing) {
this.storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
}
}
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
setJsonItem(this.store, keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId), sessionData);
}
storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn) {
setJsonItem(this.store, keyEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId), sessionData);
}
getEndToEndDeviceData(txn, func) {
func(getJsonItem(this.store, KEY_DEVICE_DATA));
}
storeEndToEndDeviceData(deviceData, txn) {
setJsonItem(this.store, KEY_DEVICE_DATA, deviceData);
}
storeEndToEndRoom(roomId, roomInfo, txn) {
setJsonItem(this.store, keyEndToEndRoomsPrefix(roomId), roomInfo);
}
getEndToEndRooms(txn, func) {
const result = {};
const prefix = keyEndToEndRoomsPrefix("");
for (let i = 0; i < this.store.length; ++i) {
const key = this.store.key(i);
if (key?.startsWith(prefix)) {
if (key !== null && key !== void 0 && key.startsWith(prefix)) {
const roomId = key.slice(prefix.length);
result[roomId] = getJsonItem(this.store, key);
}
}
func(result);
}
getSessionsNeedingBackup(limit) {
const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
const sessions = [];
for (const session in sessionsNeedingBackup) {
if (Object.prototype.hasOwnProperty.call(sessionsNeedingBackup, session)) {
// see getAllEndToEndInboundGroupSessions for the magic number explanations
const senderKey = session.slice(0, 43);
const sessionId = session.slice(44);
this.getEndToEndInboundGroupSession(senderKey, sessionId, null, sessionData => {
sessions.push({
senderKey: senderKey,
sessionId: sessionId,
sessionData: sessionData
});
});
if (limit && sessions.length >= limit) {
break;
}
}
}
return Promise.resolve(sessions);
}
countSessionsNeedingBackup() {
const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
return Promise.resolve(Object.keys(sessionsNeedingBackup).length);
}
unmarkSessionsNeedingBackup(sessions) {
const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
for (const session of sessions) {
delete sessionsNeedingBackup[session.senderKey + "/" + session.sessionId];
}
setJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP, sessionsNeedingBackup);
return Promise.resolve();
}
markSessionsNeedingBackup(sessions) {
const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
for (const session of sessions) {
@@ -318,6 +305,9 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
const key = getJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`);
func(key);
}
storeCrossSigningKeys(txn, keys) {
setJsonItem(this.store, KEY_CROSS_SIGNING_KEYS, keys);
}
storeSecretStorePrivateKey(txn, type, key) {
setJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`, key);
}
@@ -325,14 +315,15 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
return Promise.resolve(func(null));
}
}
exports.LocalStorageCryptoStore = LocalStorageCryptoStore;
function getJsonItem(store, key) {
try {
// if the key is absent, store.getItem() returns null, and
// JSON.parse(null) === null, so this returns null.
return JSON.parse(store.getItem(key));
} catch (e) {
logger.log("Error: Failed to get key %s: %s", key, e.message);
logger.log(e.stack);
_logger.logger.log("Error: Failed to get key %s: %s", key, e.message);
_logger.logger.log(e.stack);
}
return null;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,26 +1,27 @@
import { type CryptoStore, type ISession, type SessionExtended, type ISessionInfo, type IWithheld, MigrationState, type Mode, type SecretStorePrivateKeys, type InboundGroupSessionData, type IRoomEncryption } from "./base.ts";
import { type CrossSigningKeyInfo } from "../../crypto-api/index.ts";
import { CryptoStore, IDeviceData, IProblem, ISession, ISessionInfo, IWithheld, Mode, OutgoingRoomKeyRequest, ParkedSharedHistory, SecretStorePrivateKeys } from "./base";
import { IRoomKeyRequestBody } from "../index";
import { ICrossSigningKey } from "../../client";
import { IOlmDevice } from "../algorithms/megolm";
import { IRoomEncryption } from "../RoomList";
import { InboundGroupSessionData } from "../OlmDevice";
/**
* Internal module. in-memory storage for e2e.
*/
export declare class MemoryCryptoStore implements CryptoStore {
private migrationState;
private outgoingRoomKeyRequests;
private account;
private crossSigningKeys;
private privateKeys;
private sessions;
private sessionProblems;
private notifiedErrorDevices;
private inboundGroupSessions;
private inboundGroupSessionsWithheld;
private deviceData;
private rooms;
private sessionsNeedingBackup;
/**
* Returns true if this CryptoStore has ever been initialised (ie, it might contain data).
*
* Implementation of {@link CryptoStore.containsData}.
*
* @internal
*/
containsData(): Promise<boolean>;
private sharedHistoryInboundGroupSessions;
private parkedSharedHistory;
/**
* Ensure the database exists and is up-to-date.
*
@@ -36,82 +37,108 @@ export declare class MemoryCryptoStore implements CryptoStore {
*/
deleteAllData(): Promise<void>;
/**
* Get data on how much of the libolm to Rust Crypto migration has been done.
* Look for an existing outgoing room key request, and if none is found,
* add a new one
*
* Implementation of {@link CryptoStore.getMigrationState}.
*
* @internal
* @returns resolves to
* {@link OutgoingRoomKeyRequest}: either the
* same instance as passed in, or the existing one.
*/
getMigrationState(): Promise<MigrationState>;
getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise<OutgoingRoomKeyRequest>;
/**
* Set data on how much of the libolm to Rust Crypto migration has been done.
* Look for an existing room key request
*
* Implementation of {@link CryptoStore.setMigrationState}.
* @param requestBody - existing request to look for
*
* @returns resolves to the matching
* {@link OutgoingRoomKeyRequest}, or null if
* not found
*/
getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<OutgoingRoomKeyRequest | null>;
/**
* Looks for existing room key request, and returns the result synchronously.
*
* @internal
*
* @param requestBody - existing request to look for
*
* @returns
* the matching request, or null if not found
*/
setMigrationState(migrationState: MigrationState): Promise<void>;
private _getOutgoingRoomKeyRequest;
/**
* Look for room key requests by state
*
* @param wantedStates - list of acceptable states
*
* @returns resolves to the a
* {@link OutgoingRoomKeyRequest}, or null if
* there are no pending requests in those states
*/
getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise<OutgoingRoomKeyRequest | null>;
/**
*
* @returns All OutgoingRoomKeyRequests in state
*/
getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise<OutgoingRoomKeyRequest[]>;
getOutgoingRoomKeyRequestsByTarget(userId: string, deviceId: string, wantedStates: number[]): Promise<OutgoingRoomKeyRequest[]>;
/**
* Look for an existing room key request by id and state, and update it if
* found
*
* @param requestId - ID of request to update
* @param expectedState - state we expect to find the request in
* @param updates - name/value map of updates to apply
*
* @returns resolves to
* {@link OutgoingRoomKeyRequest}
* updated request, or null if no matching row was found
*/
updateOutgoingRoomKeyRequest(requestId: string, expectedState: number, updates: Partial<OutgoingRoomKeyRequest>): Promise<OutgoingRoomKeyRequest | null>;
/**
* Look for an existing room key request by id and state, and delete it if
* found
*
* @param requestId - ID of request to update
* @param expectedState - state we expect to find the request in
*
* @returns resolves once the operation is completed
*/
deleteOutgoingRoomKeyRequest(requestId: string, expectedState: number): Promise<OutgoingRoomKeyRequest | null>;
getAccount(txn: unknown, func: (accountPickle: string | null) => void): void;
storeAccount(txn: unknown, accountPickle: string): void;
getCrossSigningKeys(txn: unknown, func: (keys: Record<string, CrossSigningKeyInfo> | null) => void): void;
getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey> | null) => void): void;
getSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(txn: unknown, func: (key: SecretStorePrivateKeys[K] | null) => void, type: K): void;
storeCrossSigningKeys(txn: unknown, keys: Record<string, ICrossSigningKey>): void;
storeSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(txn: unknown, type: K, key: SecretStorePrivateKeys[K]): void;
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;
/**
* Fetch a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndSessionsBatch}.
*
* @internal
*/
getEndToEndSessionsBatch(): Promise<null | ISessionInfo[]>;
/**
* Delete a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndSessionsBatch}.
*
* @internal
*/
deleteEndToEndSessionsBatch(sessions: {
deviceKey: string;
sessionId: string;
}[]): Promise<void>;
storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void>;
getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null>;
filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]>;
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;
/**
* Count the number of Megolm sessions in the database.
*
* Implementation of {@link CryptoStore.countEndToEndInboundGroupSessions}.
*
* @internal
*/
countEndToEndInboundGroupSessions(): Promise<number>;
/**
* Fetch a batch of Megolm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndInboundGroupSessionsBatch}.
*
* @internal
*/
getEndToEndInboundGroupSessionsBatch(): Promise<null | SessionExtended[]>;
/**
* Delete a batch of Megolm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndInboundGroupSessionsBatch}.
*
* @internal
*/
deleteEndToEndInboundGroupSessionsBatch(sessions: {
senderKey: string;
sessionId: string;
}[]): Promise<void>;
storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key: string, sessionId: string, sessionData: IWithheld, txn: unknown): void;
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(): Promise<number>;
unmarkSessionsNeedingBackup(sessions: ISession[]): Promise<void>;
markSessionsNeedingBackup(sessions: ISession[]): Promise<void>;
addSharedHistoryInboundGroupSession(roomId: string, senderKey: string, sessionId: string): void;
getSharedHistoryInboundGroupSessions(roomId: string): Promise<[senderKey: string, sessionId: string][]>;
addParkedSharedHistory(roomId: string, parkedData: ParkedSharedHistory): void;
takeParkedSharedHistory(roomId: string): Promise<ParkedSharedHistory[]>;
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn?: unknown) => T): Promise<T>;
}
//# sourceMappingURL=memory-crypto-store.d.ts.map

View File

@@ -1 +1 @@
{"version":3,"file":"memory-crypto-store.d.ts","sourceRoot":"","sources":["../../../src/crypto/store/memory-crypto-store.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"memory-crypto-store.d.ts","sourceRoot":"","sources":["../../../src/crypto/store/memory-crypto-store.ts"],"names":[],"mappings":"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"}

View File

@@ -1,65 +1,52 @@
import _objectSpread from "@babel/runtime/helpers/objectSpread2";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
/*
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.
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 { safeSet } from "../../utils.js";
import { MigrationState, SESSION_BATCH_SIZE } from "./base.js";
function encodeSessionKey(senderCurve25519Key, sessionId) {
return encodeURIComponent(senderCurve25519Key) + "/" + encodeURIComponent(sessionId);
}
function decodeSessionKey(key) {
const keyParts = key.split("/");
const senderKey = decodeURIComponent(keyParts[0]);
const sessionId = decodeURIComponent(keyParts[1]);
return {
senderKey,
sessionId
};
}
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.MemoryCryptoStore = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _logger = require("../../logger");
var _utils = require("../../utils");
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } /*
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.
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.
*/
/**
* Internal module. in-memory storage for e2e.
*/
export class MemoryCryptoStore {
class MemoryCryptoStore {
constructor() {
_defineProperty(this, "migrationState", MigrationState.NOT_STARTED);
_defineProperty(this, "account", null);
_defineProperty(this, "crossSigningKeys", null);
_defineProperty(this, "privateKeys", {});
_defineProperty(this, "sessions", {});
_defineProperty(this, "inboundGroupSessions", {});
_defineProperty(this, "inboundGroupSessionsWithheld", {});
(0, _defineProperty2.default)(this, "outgoingRoomKeyRequests", []);
(0, _defineProperty2.default)(this, "account", null);
(0, _defineProperty2.default)(this, "crossSigningKeys", null);
(0, _defineProperty2.default)(this, "privateKeys", {});
(0, _defineProperty2.default)(this, "sessions", {});
(0, _defineProperty2.default)(this, "sessionProblems", {});
(0, _defineProperty2.default)(this, "notifiedErrorDevices", {});
(0, _defineProperty2.default)(this, "inboundGroupSessions", {});
(0, _defineProperty2.default)(this, "inboundGroupSessionsWithheld", {});
// Opaque device data object
_defineProperty(this, "rooms", {});
_defineProperty(this, "sessionsNeedingBackup", {});
(0, _defineProperty2.default)(this, "deviceData", null);
(0, _defineProperty2.default)(this, "rooms", {});
(0, _defineProperty2.default)(this, "sessionsNeedingBackup", {});
(0, _defineProperty2.default)(this, "sharedHistoryInboundGroupSessions", {});
(0, _defineProperty2.default)(this, "parkedSharedHistory", new Map());
}
/**
* Returns true if this CryptoStore has ever been initialised (ie, it might contain data).
*
* Implementation of {@link CryptoStore.containsData}.
*
* @internal
*/
async containsData() {
// If it contains anything, it should contain an account.
return this.account !== null;
}
// keyed by room ID
/**
* Ensure the database exists and is up-to-date.
*
@@ -82,25 +69,155 @@ export class MemoryCryptoStore {
}
/**
* Get data on how much of the libolm to Rust Crypto migration has been done.
* Look for an existing outgoing room key request, and if none is found,
* add a new one
*
* Implementation of {@link CryptoStore.getMigrationState}.
*
* @internal
* @returns resolves to
* {@link OutgoingRoomKeyRequest}: either the
* same instance as passed in, or the existing one.
*/
async getMigrationState() {
return this.migrationState;
getOrAddOutgoingRoomKeyRequest(request) {
const requestBody = request.requestBody;
return (0, _utils.promiseTry)(() => {
// first see if we already have an entry for this request.
const existing = this._getOutgoingRoomKeyRequest(requestBody);
if (existing) {
// this entry matches the request - return it.
_logger.logger.log(`already have key request outstanding for ` + `${requestBody.room_id} / ${requestBody.session_id}: ` + `not sending another`);
return existing;
}
// we got to the end of the list without finding a match
// - add the new request.
_logger.logger.log(`enqueueing key request for ${requestBody.room_id} / ` + requestBody.session_id);
this.outgoingRoomKeyRequests.push(request);
return request;
});
}
/**
* Set data on how much of the libolm to Rust Crypto migration has been done.
* Look for an existing room key request
*
* Implementation of {@link CryptoStore.setMigrationState}.
* @param requestBody - existing request to look for
*
* @returns resolves to the matching
* {@link OutgoingRoomKeyRequest}, or null if
* not found
*/
getOutgoingRoomKeyRequest(requestBody) {
return Promise.resolve(this._getOutgoingRoomKeyRequest(requestBody));
}
/**
* Looks for existing room key request, and returns the result synchronously.
*
* @internal
*
* @param requestBody - existing request to look for
*
* @returns
* the matching request, or null if not found
*/
async setMigrationState(migrationState) {
this.migrationState = migrationState;
// eslint-disable-next-line @typescript-eslint/naming-convention
_getOutgoingRoomKeyRequest(requestBody) {
for (const existing of this.outgoingRoomKeyRequests) {
if ((0, _utils.deepCompare)(existing.requestBody, requestBody)) {
return existing;
}
}
return null;
}
/**
* Look for room key requests by state
*
* @param wantedStates - list of acceptable states
*
* @returns resolves to the a
* {@link OutgoingRoomKeyRequest}, or null if
* there are no pending requests in those states
*/
getOutgoingRoomKeyRequestByState(wantedStates) {
for (const req of this.outgoingRoomKeyRequests) {
for (const state of wantedStates) {
if (req.state === state) {
return Promise.resolve(req);
}
}
}
return Promise.resolve(null);
}
/**
*
* @returns All OutgoingRoomKeyRequests in state
*/
getAllOutgoingRoomKeyRequestsByState(wantedState) {
return Promise.resolve(this.outgoingRoomKeyRequests.filter(r => r.state == wantedState));
}
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
const results = [];
for (const req of this.outgoingRoomKeyRequests) {
for (const state of wantedStates) {
if (req.state === state && req.recipients.some(recipient => recipient.userId === userId && recipient.deviceId === deviceId)) {
results.push(req);
}
}
}
return Promise.resolve(results);
}
/**
* Look for an existing room key request by id and state, and update it if
* found
*
* @param requestId - ID of request to update
* @param expectedState - state we expect to find the request in
* @param updates - name/value map of updates to apply
*
* @returns resolves to
* {@link OutgoingRoomKeyRequest}
* updated request, or null if no matching row was found
*/
updateOutgoingRoomKeyRequest(requestId, expectedState, updates) {
for (const req of this.outgoingRoomKeyRequests) {
if (req.requestId !== requestId) {
continue;
}
if (req.state !== expectedState) {
_logger.logger.warn(`Cannot update room key request from ${expectedState} ` + `as it was already updated to ${req.state}`);
return Promise.resolve(null);
}
Object.assign(req, updates);
return Promise.resolve(req);
}
return Promise.resolve(null);
}
/**
* Look for an existing room key request by id and state, and delete it if
* found
*
* @param requestId - ID of request to update
* @param expectedState - state we expect to find the request in
*
* @returns resolves once the operation is completed
*/
deleteOutgoingRoomKeyRequest(requestId, expectedState) {
for (let i = 0; i < this.outgoingRoomKeyRequests.length; i++) {
const req = this.outgoingRoomKeyRequests[i];
if (req.requestId !== requestId) {
continue;
}
if (req.state != expectedState) {
_logger.logger.warn(`Cannot delete room key request in state ${req.state} ` + `(expected ${expectedState})`);
return Promise.resolve(null);
}
this.outgoingRoomKeyRequests.splice(i, 1);
return Promise.resolve(req);
}
return Promise.resolve(null);
}
// Olm Account
@@ -118,6 +235,9 @@ export class MemoryCryptoStore {
const result = this.privateKeys[type];
func(result || null);
}
storeCrossSigningKeys(txn, keys) {
this.crossSigningKeys = keys;
}
storeSecretStorePrivateKey(txn, type, key) {
this.privateKeys[type] = key;
}
@@ -125,11 +245,7 @@ export class MemoryCryptoStore {
// Olm Sessions
countEndToEndSessions(txn, func) {
let count = 0;
for (const deviceSessions of Object.values(this.sessions)) {
count += Object.keys(deviceSessions).length;
}
func(count);
func(Object.keys(this.sessions).length);
}
getEndToEndSession(deviceKey, sessionId, txn, func) {
const deviceSessions = this.sessions[deviceKey] || {};
@@ -138,140 +254,182 @@ export class MemoryCryptoStore {
getEndToEndSessions(deviceKey, txn, func) {
func(this.sessions[deviceKey] || {});
}
getAllEndToEndSessions(txn, func) {
Object.entries(this.sessions).forEach(([deviceKey, deviceSessions]) => {
Object.entries(deviceSessions).forEach(([sessionId, session]) => {
func(_objectSpread(_objectSpread({}, session), {}, {
deviceKey,
sessionId
}));
});
});
}
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
let deviceSessions = this.sessions[deviceKey];
if (deviceSessions === undefined) {
deviceSessions = {};
this.sessions[deviceKey] = deviceSessions;
}
safeSet(deviceSessions, sessionId, sessionInfo);
(0, _utils.safeSet)(deviceSessions, sessionId, sessionInfo);
}
/**
* Fetch a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndSessionsBatch}.
*
* @internal
*/
async getEndToEndSessionsBatch() {
const result = [];
for (const deviceSessions of Object.values(this.sessions)) {
for (const session of Object.values(deviceSessions)) {
result.push(session);
if (result.length >= SESSION_BATCH_SIZE) {
return result;
}
}
}
if (result.length === 0) {
// No sessions left.
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
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] || [];
if (!problems.length) {
return null;
}
// There are fewer sessions than the batch size; return the final batch of sessions.
return result;
}
/**
* Delete a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndSessionsBatch}.
*
* @internal
*/
async deleteEndToEndSessionsBatch(sessions) {
for (const {
deviceKey,
sessionId
} of sessions) {
const deviceSessions = this.sessions[deviceKey] || {};
delete deviceSessions[sessionId];
if (Object.keys(deviceSessions).length === 0) {
// No more sessions for this device.
delete this.sessions[deviceKey];
const lastProblem = problems[problems.length - 1];
for (const problem of problems) {
if (problem.time > timestamp) {
return Object.assign({}, problem, {
fixed: lastProblem.fixed
});
}
}
if (lastProblem.fixed) {
return null;
} else {
return lastProblem;
}
}
async filterOutNotifiedErrorDevices(devices) {
const notifiedErrorDevices = this.notifiedErrorDevices;
const ret = [];
for (const device of devices) {
const {
userId,
deviceInfo
} = device;
if (userId in notifiedErrorDevices) {
if (!(deviceInfo.deviceId in notifiedErrorDevices[userId])) {
ret.push(device);
(0, _utils.safeSet)(notifiedErrorDevices[userId], deviceInfo.deviceId, true);
}
} else {
ret.push(device);
(0, _utils.safeSet)(notifiedErrorDevices, userId, {
[deviceInfo.deviceId]: true
});
}
}
return ret;
}
// Inbound Group Sessions
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
const k = encodeSessionKey(senderCurve25519Key, sessionId);
const k = senderCurve25519Key + "/" + sessionId;
func(this.inboundGroupSessions[k] || null, this.inboundGroupSessionsWithheld[k] || null);
}
getAllEndToEndInboundGroupSessions(txn, func) {
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
// (hence 43 characters long).
func({
senderKey: key.slice(0, 43),
sessionId: key.slice(44),
sessionData: this.inboundGroupSessions[key]
});
}
func(null);
}
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
const k = senderCurve25519Key + "/" + sessionId;
if (this.inboundGroupSessions[k] === undefined) {
this.inboundGroupSessions[k] = sessionData;
}
}
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
const k = encodeSessionKey(senderCurve25519Key, sessionId);
this.inboundGroupSessions[k] = sessionData;
this.inboundGroupSessions[senderCurve25519Key + "/" + sessionId] = sessionData;
}
storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn) {
const k = senderCurve25519Key + "/" + sessionId;
this.inboundGroupSessionsWithheld[k] = sessionData;
}
/**
* Count the number of Megolm sessions in the database.
*
* Implementation of {@link CryptoStore.countEndToEndInboundGroupSessions}.
*
* @internal
*/
async countEndToEndInboundGroupSessions() {
return Object.keys(this.inboundGroupSessions).length;
// Device Data
getEndToEndDeviceData(txn, func) {
func(this.deviceData);
}
/**
* Fetch a batch of Megolm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndInboundGroupSessionsBatch}.
*
* @internal
*/
async getEndToEndInboundGroupSessionsBatch() {
const result = [];
for (const [key, session] of Object.entries(this.inboundGroupSessions)) {
result.push(_objectSpread(_objectSpread({}, decodeSessionKey(key)), {}, {
sessionData: session,
needsBackup: key in this.sessionsNeedingBackup
}));
if (result.length >= SESSION_BATCH_SIZE) {
return result;
}
}
if (result.length === 0) {
// No sessions left.
return null;
}
// There are fewer sessions than the batch size; return the final batch of sessions.
return result;
}
/**
* Delete a batch of Megolm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndInboundGroupSessionsBatch}.
*
* @internal
*/
async deleteEndToEndInboundGroupSessionsBatch(sessions) {
for (const {
senderKey,
sessionId
} of sessions) {
const k = encodeSessionKey(senderKey, sessionId);
delete this.inboundGroupSessions[k];
}
storeEndToEndDeviceData(deviceData, txn) {
this.deviceData = deviceData;
}
// E2E rooms
storeEndToEndRoom(roomId, roomInfo, txn) {
this.rooms[roomId] = roomInfo;
}
getEndToEndRooms(txn, func) {
func(this.rooms);
}
getSessionsNeedingBackup(limit) {
const sessions = [];
for (const session in this.sessionsNeedingBackup) {
if (this.inboundGroupSessions[session]) {
sessions.push({
senderKey: session.slice(0, 43),
sessionId: session.slice(44),
sessionData: this.inboundGroupSessions[session]
});
if (limit && session.length >= limit) {
break;
}
}
}
return Promise.resolve(sessions);
}
countSessionsNeedingBackup() {
return Promise.resolve(Object.keys(this.sessionsNeedingBackup).length);
}
unmarkSessionsNeedingBackup(sessions) {
for (const session of sessions) {
const sessionKey = session.senderKey + "/" + session.sessionId;
delete this.sessionsNeedingBackup[sessionKey];
}
return Promise.resolve();
}
markSessionsNeedingBackup(sessions) {
for (const session of sessions) {
const sessionKey = encodeSessionKey(session.senderKey, session.sessionId);
const sessionKey = session.senderKey + "/" + session.sessionId;
this.sessionsNeedingBackup[sessionKey] = true;
}
return Promise.resolve();
}
addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId) {
const sessions = this.sharedHistoryInboundGroupSessions[roomId] || [];
sessions.push([senderKey, sessionId]);
this.sharedHistoryInboundGroupSessions[roomId] = sessions;
}
getSharedHistoryInboundGroupSessions(roomId) {
return Promise.resolve(this.sharedHistoryInboundGroupSessions[roomId] || []);
}
addParkedSharedHistory(roomId, parkedData) {
var _this$parkedSharedHis;
const parked = (_this$parkedSharedHis = this.parkedSharedHistory.get(roomId)) !== null && _this$parkedSharedHis !== void 0 ? _this$parkedSharedHis : [];
parked.push(parkedData);
this.parkedSharedHistory.set(roomId, parked);
}
takeParkedSharedHistory(roomId) {
var _this$parkedSharedHis2;
const parked = (_this$parkedSharedHis2 = this.parkedSharedHistory.get(roomId)) !== null && _this$parkedSharedHis2 !== void 0 ? _this$parkedSharedHis2 : [];
this.parkedSharedHistory.delete(roomId);
return Promise.resolve(parked);
}
// Session key backups
@@ -279,4 +437,5 @@ export class MemoryCryptoStore {
return Promise.resolve(func(null));
}
}
exports.MemoryCryptoStore = MemoryCryptoStore;
//# sourceMappingURL=memory-crypto-store.js.map

File diff suppressed because one or more lines are too long