1
0
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:
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 "../../olm-loader";
import * as olmlib from "../../../src/crypto/olmlib"; import * as olmlib from "../../../src/crypto/olmlib";
import { IObject } 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 { MatrixEvent } from "../../../src/models/event";
import { TestClient } from "../../TestClient"; import { TestClient } from "../../TestClient";
import { makeTestClients } from "./verification/util"; import { makeTestClients } from "./verification/util";
@@ -28,7 +27,12 @@ import { ClientEvent, ICreateClientOpts, ICrossSigningKey, MatrixClient } from "
import { DeviceInfo } from "../../../src/crypto/deviceinfo"; import { DeviceInfo } from "../../../src/crypto/deviceinfo";
import { ISignatures } from "../../../src/@types/signed"; import { ISignatures } from "../../../src/@types/signed";
import { ICurve25519AuthData } from "../../../src/crypto/keybackup"; 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( async function makeTestClient(
userInfo: { userId: string; deviceId: string }, userInfo: { userId: string; deviceId: string },
@@ -77,6 +81,22 @@ describe("Secrets", function () {
return global.Olm.init(); 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 () { it("should store and retrieve a secret", async function () {
const key = new Uint8Array(16); const key = new Uint8Array(16);
for (let i = 0; i < 16; i++) key[i] = i; 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 { IMinimalEvent, IRoomEvent, IStateEvent } from "./sync-accumulator";
import { import {
CrossSigningKey, CrossSigningKey,
IAddSecretStorageKeyOpts,
ICreateSecretStorageOpts, ICreateSecretStorageOpts,
IEncryptedEventInfo, IEncryptedEventInfo,
IImportRoomKeysOpts, IImportRoomKeysOpts,
@@ -207,7 +206,7 @@ import { CryptoBackend } from "./common-crypto/CryptoBackend";
import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants"; import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants";
import { CryptoApi } from "./crypto-api"; import { CryptoApi } from "./crypto-api";
import { DeviceInfoMap } from "./crypto/DeviceList"; import { DeviceInfoMap } from "./crypto/DeviceList";
import { SecretStorageKeyDescription } from "./secret-storage"; import { AddSecretStorageKeyOpts, SecretStorageKeyDescription } from "./secret-storage";
export type Store = IStore; export type Store = IStore;
@@ -2861,7 +2860,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*/ */
public addSecretStorageKey( public addSecretStorageKey(
algorithm: string, algorithm: string,
opts: IAddSecretStorageKeyOpts, opts: AddSecretStorageKeyOpts,
keyName?: string, keyName?: string,
): Promise<{ keyId: string; keyInfo: SecretStorageKeyDescription }> { ): Promise<{ keyId: string; keyInfo: SecretStorageKeyDescription }> {
if (!this.crypto) { if (!this.crypto) {

View File

@@ -30,8 +30,7 @@ import {
} from "../client"; } from "../client";
import { IKeyBackupInfo } from "./keybackup"; import { IKeyBackupInfo } from "./keybackup";
import { TypedEventEmitter } from "../models/typed-event-emitter"; import { TypedEventEmitter } from "../models/typed-event-emitter";
import { IAccountDataClient } from "./SecretStorage"; import { AccountDataClient, SecretStorageKeyDescription } from "../secret-storage";
import { SecretStorageKeyDescription } from "../secret-storage";
interface ICrossSigningKeys { interface ICrossSigningKeys {
authUpload: IBootstrapCrossSigningOpts["authUploadDeviceSigningKeys"]; authUpload: IBootstrapCrossSigningOpts["authUploadDeviceSigningKeys"];
@@ -238,7 +237,7 @@ export class EncryptionSetupOperation {
*/ */
class AccountDataClientAdapter class AccountDataClientAdapter
extends TypedEventEmitter<ClientEvent.AccountData, ClientEventHandlerMap> extends TypedEventEmitter<ClientEvent.AccountData, ClientEventHandlerMap>
implements IAccountDataClient implements AccountDataClient
{ {
// //
public readonly values = new Map<string, MatrixEvent>(); public readonly values = new Map<string, MatrixEvent>();

View File

@@ -21,19 +21,27 @@ import * as olmlib from "./olmlib";
import { randomString } from "../randomstring"; import { randomString } from "../randomstring";
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes"; import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes";
import { ICryptoCallbacks, IEncryptedContent } from "."; import { ICryptoCallbacks, IEncryptedContent } from ".";
import { IContent, MatrixEvent } from "../models/event"; import { MatrixEvent } from "../models/event";
import { ClientEvent, ClientEventHandlerMap, MatrixClient } from "../client"; import { ClientEvent, MatrixClient } from "../client";
import { IAddSecretStorageKeyOpts } from "./api";
import { TypedEventEmitter } from "../models/typed-event-emitter";
import { defer, IDeferred } from "../utils"; import { defer, IDeferred } from "../utils";
import { ToDeviceMessageId } from "../@types/event"; 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"; /* re-exports for backwards compatibility */
export {
// Some of the key functions use a tuple and some use an object... AccountDataClient as IAccountDataClient,
export type SecretStorageKeyTuple = [keyId: string, keyInfo: SecretStorageKeyDescription]; SecretStorageKeyTuple,
export type SecretStorageKeyObject = { keyId: string; keyInfo: SecretStorageKeyDescription }; SecretStorageKeyObject,
SECRET_STORAGE_ALGORITHM_V1_AES,
} from "../secret-storage";
export interface ISecretRequest { export interface ISecretRequest {
requestId: string; requestId: string;
@@ -41,13 +49,6 @@ export interface ISecretRequest {
cancel: (reason: string) => void; 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 { interface ISecretRequestInternal {
name: string; name: string;
devices: 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 // 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. // secret sharing which are really two separate things, even though they share an MSC.
public constructor( public constructor(
private readonly accountDataAdapter: IAccountDataClient, private readonly accountDataAdapter: AccountDataClient,
private readonly cryptoCallbacks: ICryptoCallbacks, private readonly cryptoCallbacks: ICryptoCallbacks,
private readonly baseApis: B, private readonly baseApis: B,
) {} ) {}
@@ -125,7 +126,7 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
*/ */
public async addKey( public async addKey(
algorithm: string, algorithm: string,
opts: IAddSecretStorageKeyOpts = {}, opts: AddSecretStorageKeyOpts = {},
keyId?: string, keyId?: string,
): Promise<SecretStorageKeyObject> { ): Promise<SecretStorageKeyObject> {
if (algorithm !== SECRET_STORAGE_ALGORITHM_V1_AES) { if (algorithm !== SECRET_STORAGE_ALGORITHM_V1_AES) {

View File

@@ -16,10 +16,11 @@ limitations under the License.
import { DeviceInfo } from "./deviceinfo"; import { DeviceInfo } from "./deviceinfo";
import { IKeyBackupInfo } from "./keybackup"; import { IKeyBackupInfo } from "./keybackup";
import type { PassphraseInfo } from "../secret-storage"; import type { AddSecretStorageKeyOpts } from "../secret-storage";
/* re-exports for backwards compatibility. */ /* re-exports for backwards compatibility. */
export type { export type {
AddSecretStorageKeyOpts as IAddSecretStorageKeyOpts,
PassphraseInfo as IPassphraseInfo, PassphraseInfo as IPassphraseInfo,
SecretStorageKeyDescription as ISecretStorageKeyInfo, SecretStorageKeyDescription as ISecretStorageKeyInfo,
} from "../secret-storage"; } from "../secret-storage";
@@ -65,7 +66,7 @@ export interface IEncryptedEventInfo {
} }
export interface IRecoveryKey { export interface IRecoveryKey {
keyInfo?: IAddSecretStorageKeyOpts; keyInfo?: AddSecretStorageKeyOpts;
privateKey: Uint8Array; privateKey: Uint8Array;
encodedPrivateKey?: string; encodedPrivateKey?: string;
} }
@@ -105,13 +106,6 @@ export interface ICreateSecretStorageOpts {
getKeyBackupPassphrase?: () => Promise<Uint8Array>; getKeyBackupPassphrase?: () => Promise<Uint8Array>;
} }
export interface IAddSecretStorageKeyOpts {
pubkey?: string;
passphrase?: PassphraseInfo;
name?: string;
key?: Uint8Array;
}
export interface IImportOpts { export interface IImportOpts {
stage: string; // TODO: Enum stage: string; // TODO: Enum
successes: number; successes: number;

View File

@@ -34,21 +34,8 @@ import type { DecryptionAlgorithm, EncryptionAlgorithm } from "./algorithms";
import * as algorithms from "./algorithms"; import * as algorithms from "./algorithms";
import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from "./CrossSigning"; import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from "./CrossSigning";
import { EncryptionSetupBuilder } from "./EncryptionSetup"; import { EncryptionSetupBuilder } from "./EncryptionSetup";
import { import { ISecretRequest, SecretStorage } from "./SecretStorage";
IAccountDataClient, import { ICreateSecretStorageOpts, IEncryptedEventInfo, IImportRoomKeysOpts, IRecoveryKey } from "./api";
ISecretRequest,
SECRET_STORAGE_ALGORITHM_V1_AES,
SecretStorage,
SecretStorageKeyObject,
SecretStorageKeyTuple,
} from "./SecretStorage";
import {
IAddSecretStorageKeyOpts,
ICreateSecretStorageOpts,
IEncryptedEventInfo,
IImportRoomKeysOpts,
IRecoveryKey,
} from "./api";
import { OutgoingRoomKeyRequestManager } from "./OutgoingRoomKeyRequestManager"; import { OutgoingRoomKeyRequestManager } from "./OutgoingRoomKeyRequestManager";
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store"; import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store";
import { VerificationBase } from "./verification/Base"; import { VerificationBase } from "./verification/Base";
@@ -90,7 +77,14 @@ import { IMessage } from "./algorithms/olm";
import { CryptoBackend, OnSyncCompletedData } from "../common-crypto/CryptoBackend"; import { CryptoBackend, OnSyncCompletedData } from "../common-crypto/CryptoBackend";
import { RoomState, RoomStateEvent } from "../models/room-state"; import { RoomState, RoomStateEvent } from "../models/room-state";
import { MapWithDefault, recursiveMapToObject } from "../utils"; 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; const DeviceVerification = DeviceInfo.DeviceVerification;
@@ -529,7 +523,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
this.crossSigningInfo = new CrossSigningInfo(userId, cryptoCallbacks, cacheCallbacks); this.crossSigningInfo = new CrossSigningInfo(userId, cryptoCallbacks, cacheCallbacks);
// Yes, we pass the client twice here: see SecretStorage // 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); this.dehydrationManager = new DehydrationManager(this);
// Assuming no app-supplied callback, default to getting from SSSS. // 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; let newKeyId: string | null = null;
// create a new SSSS key and set it as default // 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) { if (privateKey) {
opts.key = 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) // 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) // 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. // 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); newKeyId = await createSSSS(keyInfo, privateKey);
} else if (!storageExists && keyBackupInfo) { } else if (!storageExists && keyBackupInfo) {
// we have an existing backup, but no SSSS // 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?.()); const backupKey = (await this.getSessionBackupPrivateKey()) || (await getKeyBackupPassphrase?.());
// create a new SSSS key and use the backup key as the new SSSS key // 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) { if (keyBackupInfo.auth_data.private_key_salt && keyBackupInfo.auth_data.private_key_iterations) {
// FIXME: ??? // FIXME: ???
@@ -1111,7 +1105,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
public addSecretStorageKey( public addSecretStorageKey(
algorithm: string, algorithm: string,
opts: IAddSecretStorageKeyOpts, opts: AddSecretStorageKeyOpts,
keyID?: string, keyID?: string,
): Promise<SecretStorageKeyObject> { ): Promise<SecretStorageKeyObject> {
return this.secretStorage.addKey(algorithm, opts, keyID); 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 * @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. * Common base interface for Secret Storage Keys.
* *
@@ -86,3 +91,48 @@ export interface PassphraseInfo {
/** The number of bits to generate. Defaults to 256. */ /** The number of bits to generate. Defaults to 256. */
bits?: number; 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<{}>;
}