You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-26 17:03:12 +03:00
Move SecretStorage-related interfaces out to new module (#3244)
* Remove redundant `IAccountDataClient.getAccountData` This is never called, so we may as well get rid of it * Move a few more interfaces into `secret-storage.ts` * Use interfaces from `secret-storage` * Move IAccountDataClient to secret-storage * Use `AccountDataClient` from `secret-storage` * move SECRET_STORAGE_ALGORITHM_V1_AES to secret-storage * Use `SECRET_STORAGE_ALGORITHM_V1_AES` from `secret-storage` * Add a test case for the quality gate * Update src/secret-storage.ts
This commit is contained in:
committed by
GitHub
parent
9a840d484c
commit
6ebbc15359
@@ -17,7 +17,6 @@ limitations under the License.
|
||||
import "../../olm-loader";
|
||||
import * as olmlib from "../../../src/crypto/olmlib";
|
||||
import { IObject } from "../../../src/crypto/olmlib";
|
||||
import { SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/crypto/SecretStorage";
|
||||
import { MatrixEvent } from "../../../src/models/event";
|
||||
import { TestClient } from "../../TestClient";
|
||||
import { makeTestClients } from "./verification/util";
|
||||
@@ -28,7 +27,12 @@ import { ClientEvent, ICreateClientOpts, ICrossSigningKey, MatrixClient } from "
|
||||
import { DeviceInfo } from "../../../src/crypto/deviceinfo";
|
||||
import { ISignatures } from "../../../src/@types/signed";
|
||||
import { ICurve25519AuthData } from "../../../src/crypto/keybackup";
|
||||
import { SecretStorageKeyDescription } from "../../../src/secret-storage";
|
||||
import {
|
||||
SecretStorageKeyDescription,
|
||||
SECRET_STORAGE_ALGORITHM_V1_AES,
|
||||
AccountDataClient,
|
||||
} from "../../../src/secret-storage";
|
||||
import { SecretStorage } from "../../../src/crypto/SecretStorage";
|
||||
|
||||
async function makeTestClient(
|
||||
userInfo: { userId: string; deviceId: string },
|
||||
@@ -77,6 +81,22 @@ describe("Secrets", function () {
|
||||
return global.Olm.init();
|
||||
});
|
||||
|
||||
it("should allow storing a default key", async function () {
|
||||
const accountDataAdapter = {
|
||||
getAccountDataFromServer: jest.fn().mockResolvedValue(null),
|
||||
setAccountData: jest.fn().mockResolvedValue({}),
|
||||
};
|
||||
const secretStorage = new SecretStorage(accountDataAdapter as unknown as AccountDataClient, {}, undefined);
|
||||
const result = await secretStorage.addKey("m.secret_storage.v1.aes-hmac-sha2");
|
||||
|
||||
// it should have made up a 32-character key id
|
||||
expect(result.keyId.length).toEqual(32);
|
||||
expect(accountDataAdapter.setAccountData).toHaveBeenCalledWith(
|
||||
`m.secret_storage.key.${result.keyId}`,
|
||||
result.keyInfo,
|
||||
);
|
||||
});
|
||||
|
||||
it("should store and retrieve a secret", async function () {
|
||||
const key = new Uint8Array(16);
|
||||
for (let i = 0; i < 16; i++) key[i] = i;
|
||||
|
||||
@@ -101,7 +101,6 @@ import { IAuthData, IAuthDict } from "./interactive-auth";
|
||||
import { IMinimalEvent, IRoomEvent, IStateEvent } from "./sync-accumulator";
|
||||
import {
|
||||
CrossSigningKey,
|
||||
IAddSecretStorageKeyOpts,
|
||||
ICreateSecretStorageOpts,
|
||||
IEncryptedEventInfo,
|
||||
IImportRoomKeysOpts,
|
||||
@@ -207,7 +206,7 @@ import { CryptoBackend } from "./common-crypto/CryptoBackend";
|
||||
import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants";
|
||||
import { CryptoApi } from "./crypto-api";
|
||||
import { DeviceInfoMap } from "./crypto/DeviceList";
|
||||
import { SecretStorageKeyDescription } from "./secret-storage";
|
||||
import { AddSecretStorageKeyOpts, SecretStorageKeyDescription } from "./secret-storage";
|
||||
|
||||
export type Store = IStore;
|
||||
|
||||
@@ -2861,7 +2860,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*/
|
||||
public addSecretStorageKey(
|
||||
algorithm: string,
|
||||
opts: IAddSecretStorageKeyOpts,
|
||||
opts: AddSecretStorageKeyOpts,
|
||||
keyName?: string,
|
||||
): Promise<{ keyId: string; keyInfo: SecretStorageKeyDescription }> {
|
||||
if (!this.crypto) {
|
||||
|
||||
@@ -30,8 +30,7 @@ import {
|
||||
} from "../client";
|
||||
import { IKeyBackupInfo } from "./keybackup";
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
||||
import { IAccountDataClient } from "./SecretStorage";
|
||||
import { SecretStorageKeyDescription } from "../secret-storage";
|
||||
import { AccountDataClient, SecretStorageKeyDescription } from "../secret-storage";
|
||||
|
||||
interface ICrossSigningKeys {
|
||||
authUpload: IBootstrapCrossSigningOpts["authUploadDeviceSigningKeys"];
|
||||
@@ -238,7 +237,7 @@ export class EncryptionSetupOperation {
|
||||
*/
|
||||
class AccountDataClientAdapter
|
||||
extends TypedEventEmitter<ClientEvent.AccountData, ClientEventHandlerMap>
|
||||
implements IAccountDataClient
|
||||
implements AccountDataClient
|
||||
{
|
||||
//
|
||||
public readonly values = new Map<string, MatrixEvent>();
|
||||
|
||||
@@ -21,19 +21,27 @@ import * as olmlib from "./olmlib";
|
||||
import { randomString } from "../randomstring";
|
||||
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes";
|
||||
import { ICryptoCallbacks, IEncryptedContent } from ".";
|
||||
import { IContent, MatrixEvent } from "../models/event";
|
||||
import { ClientEvent, ClientEventHandlerMap, MatrixClient } from "../client";
|
||||
import { IAddSecretStorageKeyOpts } from "./api";
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
||||
import { MatrixEvent } from "../models/event";
|
||||
import { ClientEvent, MatrixClient } from "../client";
|
||||
import { defer, IDeferred } from "../utils";
|
||||
import { ToDeviceMessageId } from "../@types/event";
|
||||
import { SecretStorageKeyDescription, SecretStorageKeyDescriptionAesV1 } from "../secret-storage";
|
||||
import {
|
||||
SecretStorageKeyDescription,
|
||||
SecretStorageKeyDescriptionAesV1,
|
||||
SecretStorageKeyTuple,
|
||||
SecretStorageKeyObject,
|
||||
AddSecretStorageKeyOpts,
|
||||
AccountDataClient,
|
||||
SECRET_STORAGE_ALGORITHM_V1_AES,
|
||||
} from "../secret-storage";
|
||||
|
||||
export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2";
|
||||
|
||||
// Some of the key functions use a tuple and some use an object...
|
||||
export type SecretStorageKeyTuple = [keyId: string, keyInfo: SecretStorageKeyDescription];
|
||||
export type SecretStorageKeyObject = { keyId: string; keyInfo: SecretStorageKeyDescription };
|
||||
/* re-exports for backwards compatibility */
|
||||
export {
|
||||
AccountDataClient as IAccountDataClient,
|
||||
SecretStorageKeyTuple,
|
||||
SecretStorageKeyObject,
|
||||
SECRET_STORAGE_ALGORITHM_V1_AES,
|
||||
} from "../secret-storage";
|
||||
|
||||
export interface ISecretRequest {
|
||||
requestId: string;
|
||||
@@ -41,13 +49,6 @@ export interface ISecretRequest {
|
||||
cancel: (reason: string) => void;
|
||||
}
|
||||
|
||||
export interface IAccountDataClient extends TypedEventEmitter<ClientEvent.AccountData, ClientEventHandlerMap> {
|
||||
// Subset of MatrixClient (which also uses any for the event content)
|
||||
getAccountDataFromServer: <T extends { [k: string]: any }>(eventType: string) => Promise<T>;
|
||||
getAccountData: (eventType: string) => IContent | null;
|
||||
setAccountData: (eventType: string, content: any) => Promise<{}>;
|
||||
}
|
||||
|
||||
interface ISecretRequestInternal {
|
||||
name: string;
|
||||
devices: string[];
|
||||
@@ -80,7 +81,7 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
|
||||
// A better solution would probably be to split this class up into secret storage and
|
||||
// secret sharing which are really two separate things, even though they share an MSC.
|
||||
public constructor(
|
||||
private readonly accountDataAdapter: IAccountDataClient,
|
||||
private readonly accountDataAdapter: AccountDataClient,
|
||||
private readonly cryptoCallbacks: ICryptoCallbacks,
|
||||
private readonly baseApis: B,
|
||||
) {}
|
||||
@@ -125,7 +126,7 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
|
||||
*/
|
||||
public async addKey(
|
||||
algorithm: string,
|
||||
opts: IAddSecretStorageKeyOpts = {},
|
||||
opts: AddSecretStorageKeyOpts = {},
|
||||
keyId?: string,
|
||||
): Promise<SecretStorageKeyObject> {
|
||||
if (algorithm !== SECRET_STORAGE_ALGORITHM_V1_AES) {
|
||||
|
||||
@@ -16,10 +16,11 @@ limitations under the License.
|
||||
|
||||
import { DeviceInfo } from "./deviceinfo";
|
||||
import { IKeyBackupInfo } from "./keybackup";
|
||||
import type { PassphraseInfo } from "../secret-storage";
|
||||
import type { AddSecretStorageKeyOpts } from "../secret-storage";
|
||||
|
||||
/* re-exports for backwards compatibility. */
|
||||
export type {
|
||||
AddSecretStorageKeyOpts as IAddSecretStorageKeyOpts,
|
||||
PassphraseInfo as IPassphraseInfo,
|
||||
SecretStorageKeyDescription as ISecretStorageKeyInfo,
|
||||
} from "../secret-storage";
|
||||
@@ -65,7 +66,7 @@ export interface IEncryptedEventInfo {
|
||||
}
|
||||
|
||||
export interface IRecoveryKey {
|
||||
keyInfo?: IAddSecretStorageKeyOpts;
|
||||
keyInfo?: AddSecretStorageKeyOpts;
|
||||
privateKey: Uint8Array;
|
||||
encodedPrivateKey?: string;
|
||||
}
|
||||
@@ -105,13 +106,6 @@ export interface ICreateSecretStorageOpts {
|
||||
getKeyBackupPassphrase?: () => Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
export interface IAddSecretStorageKeyOpts {
|
||||
pubkey?: string;
|
||||
passphrase?: PassphraseInfo;
|
||||
name?: string;
|
||||
key?: Uint8Array;
|
||||
}
|
||||
|
||||
export interface IImportOpts {
|
||||
stage: string; // TODO: Enum
|
||||
successes: number;
|
||||
|
||||
@@ -34,21 +34,8 @@ import type { DecryptionAlgorithm, EncryptionAlgorithm } from "./algorithms";
|
||||
import * as algorithms from "./algorithms";
|
||||
import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from "./CrossSigning";
|
||||
import { EncryptionSetupBuilder } from "./EncryptionSetup";
|
||||
import {
|
||||
IAccountDataClient,
|
||||
ISecretRequest,
|
||||
SECRET_STORAGE_ALGORITHM_V1_AES,
|
||||
SecretStorage,
|
||||
SecretStorageKeyObject,
|
||||
SecretStorageKeyTuple,
|
||||
} from "./SecretStorage";
|
||||
import {
|
||||
IAddSecretStorageKeyOpts,
|
||||
ICreateSecretStorageOpts,
|
||||
IEncryptedEventInfo,
|
||||
IImportRoomKeysOpts,
|
||||
IRecoveryKey,
|
||||
} from "./api";
|
||||
import { ISecretRequest, SecretStorage } from "./SecretStorage";
|
||||
import { ICreateSecretStorageOpts, IEncryptedEventInfo, IImportRoomKeysOpts, IRecoveryKey } from "./api";
|
||||
import { OutgoingRoomKeyRequestManager } from "./OutgoingRoomKeyRequestManager";
|
||||
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store";
|
||||
import { VerificationBase } from "./verification/Base";
|
||||
@@ -90,7 +77,14 @@ import { IMessage } from "./algorithms/olm";
|
||||
import { CryptoBackend, OnSyncCompletedData } from "../common-crypto/CryptoBackend";
|
||||
import { RoomState, RoomStateEvent } from "../models/room-state";
|
||||
import { MapWithDefault, recursiveMapToObject } from "../utils";
|
||||
import { SecretStorageKeyDescription } from "../secret-storage";
|
||||
import {
|
||||
AccountDataClient,
|
||||
AddSecretStorageKeyOpts,
|
||||
SecretStorageKeyDescription,
|
||||
SecretStorageKeyObject,
|
||||
SecretStorageKeyTuple,
|
||||
SECRET_STORAGE_ALGORITHM_V1_AES,
|
||||
} from "../secret-storage";
|
||||
|
||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||
|
||||
@@ -529,7 +523,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
|
||||
this.crossSigningInfo = new CrossSigningInfo(userId, cryptoCallbacks, cacheCallbacks);
|
||||
// Yes, we pass the client twice here: see SecretStorage
|
||||
this.secretStorage = new SecretStorage(baseApis as IAccountDataClient, cryptoCallbacks, baseApis);
|
||||
this.secretStorage = new SecretStorage(baseApis as AccountDataClient, cryptoCallbacks, baseApis);
|
||||
this.dehydrationManager = new DehydrationManager(this);
|
||||
|
||||
// Assuming no app-supplied callback, default to getting from SSSS.
|
||||
@@ -910,7 +904,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
let newKeyId: string | null = null;
|
||||
|
||||
// create a new SSSS key and set it as default
|
||||
const createSSSS = async (opts: IAddSecretStorageKeyOpts, privateKey?: Uint8Array): Promise<string> => {
|
||||
const createSSSS = async (opts: AddSecretStorageKeyOpts, privateKey?: Uint8Array): Promise<string> => {
|
||||
if (privateKey) {
|
||||
opts.key = privateKey;
|
||||
}
|
||||
@@ -984,7 +978,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
// secrets using it, in theory. We could move them to the new key but a)
|
||||
// that would mean we'd need to prompt for the old passphrase, and b)
|
||||
// it's not clear that would be the right thing to do anyway.
|
||||
const { keyInfo = {} as IAddSecretStorageKeyOpts, privateKey } = await createSecretStorageKey();
|
||||
const { keyInfo = {} as AddSecretStorageKeyOpts, privateKey } = await createSecretStorageKey();
|
||||
newKeyId = await createSSSS(keyInfo, privateKey);
|
||||
} else if (!storageExists && keyBackupInfo) {
|
||||
// we have an existing backup, but no SSSS
|
||||
@@ -995,7 +989,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
const backupKey = (await this.getSessionBackupPrivateKey()) || (await getKeyBackupPassphrase?.());
|
||||
|
||||
// create a new SSSS key and use the backup key as the new SSSS key
|
||||
const opts = {} as IAddSecretStorageKeyOpts;
|
||||
const opts = {} as AddSecretStorageKeyOpts;
|
||||
|
||||
if (keyBackupInfo.auth_data.private_key_salt && keyBackupInfo.auth_data.private_key_iterations) {
|
||||
// FIXME: ???
|
||||
@@ -1111,7 +1105,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
|
||||
public addSecretStorageKey(
|
||||
algorithm: string,
|
||||
opts: IAddSecretStorageKeyOpts,
|
||||
opts: AddSecretStorageKeyOpts,
|
||||
keyID?: string,
|
||||
): Promise<SecretStorageKeyObject> {
|
||||
return this.secretStorage.addKey(algorithm, opts, keyID);
|
||||
|
||||
@@ -20,6 +20,11 @@ limitations under the License.
|
||||
* @see https://spec.matrix.org/v1.6/client-server-api/#storage
|
||||
*/
|
||||
|
||||
import { TypedEventEmitter } from "./models/typed-event-emitter";
|
||||
import { ClientEvent, ClientEventHandlerMap } from "./client";
|
||||
|
||||
export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2";
|
||||
|
||||
/**
|
||||
* Common base interface for Secret Storage Keys.
|
||||
*
|
||||
@@ -86,3 +91,48 @@ export interface PassphraseInfo {
|
||||
/** The number of bits to generate. Defaults to 256. */
|
||||
bits?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link SecretStorage#addKey}.
|
||||
*/
|
||||
export interface AddSecretStorageKeyOpts {
|
||||
pubkey?: string;
|
||||
passphrase?: PassphraseInfo;
|
||||
name?: string;
|
||||
key?: Uint8Array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return type for {@link SecretStorage#getKey}.
|
||||
*/
|
||||
export type SecretStorageKeyTuple = [keyId: string, keyInfo: SecretStorageKeyDescription];
|
||||
|
||||
/**
|
||||
* Return type for {@link SecretStorage#addKey}.
|
||||
*/
|
||||
export type SecretStorageKeyObject = { keyId: string; keyInfo: SecretStorageKeyDescription };
|
||||
|
||||
/** Interface for managing account data on the server.
|
||||
*
|
||||
* A subset of {@link MatrixClient}.
|
||||
*/
|
||||
export interface AccountDataClient extends TypedEventEmitter<ClientEvent.AccountData, ClientEventHandlerMap> {
|
||||
/**
|
||||
* Get account data event of given type for the current user. This variant
|
||||
* gets account data directly from the homeserver if the local store is not
|
||||
* ready, which can be useful very early in startup before the initial sync.
|
||||
*
|
||||
* @param eventType - The type of account data
|
||||
* @returns The contents of the given account data event.
|
||||
*/
|
||||
getAccountDataFromServer: <T extends Record<string, any>>(eventType: string) => Promise<T>;
|
||||
|
||||
/**
|
||||
* Set account data event for the current user, with retries
|
||||
*
|
||||
* @param eventType - The type of account data
|
||||
* @param content - the content object to be set
|
||||
* @returns an empty object
|
||||
*/
|
||||
setAccountData: (eventType: string, content: any) => Promise<{}>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user