1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-25 05:23:13 +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:
Richard van der Hoff
2023-04-05 12:42:15 +01:00
committed by GitHub
parent 9a840d484c
commit 6ebbc15359
7 changed files with 114 additions and 57 deletions

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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>();

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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<{}>;
}