You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-09 10:22:46 +03:00
Element-R: Store cross signing keys in secret storage (#3498)
* Store cross signing keys in secret storage * Update `bootstrapSecretStorage` doc * Throw error when `createSecretStorageKey` is not set * Move mocking functions * Store cross signing keys and user signing keys * Fix `awaitCrossSigningKeyUpload` documentation * Remove useless comment * Fix formatting after merge conflict
This commit is contained in:
@@ -19,7 +19,8 @@ import "fake-indexeddb/auto";
|
|||||||
import { IDBFactory } from "fake-indexeddb";
|
import { IDBFactory } from "fake-indexeddb";
|
||||||
|
|
||||||
import { CRYPTO_BACKENDS, InitCrypto } from "../../test-utils/test-utils";
|
import { CRYPTO_BACKENDS, InitCrypto } from "../../test-utils/test-utils";
|
||||||
import { createClient, MatrixClient, IAuthDict, UIAuthCallback } from "../../../src";
|
import { createClient, IAuthDict, MatrixClient } from "../../../src";
|
||||||
|
import { mockSetupCrossSigningRequests } from "../../test-utils/mockEndpoints";
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
// reset fake-indexeddb after each test, to make sure we don't leak connections
|
// reset fake-indexeddb after each test, to make sure we don't leak connections
|
||||||
@@ -62,45 +63,14 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: s
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock the requests needed to set up cross signing
|
* Create cross-signing keys and publish the keys
|
||||||
*
|
|
||||||
* Return `{}` for `GET _matrix/client/r0/user/:userId/account_data/:type` request
|
|
||||||
* Return `{}` for `POST _matrix/client/v3/keys/signatures/upload` request (named `upload-sigs` for fetchMock check)
|
|
||||||
* Return `{}` for `POST /_matrix/client/(unstable|v3)/keys/device_signing/upload` request (named `upload-keys` for fetchMock check)
|
|
||||||
*/
|
|
||||||
function mockSetupCrossSigningRequests(): void {
|
|
||||||
// have account_data requests return an empty object
|
|
||||||
fetchMock.get("express:/_matrix/client/r0/user/:userId/account_data/:type", {});
|
|
||||||
|
|
||||||
// we expect a request to upload signatures for our device ...
|
|
||||||
fetchMock.post({ url: "path:/_matrix/client/v3/keys/signatures/upload", name: "upload-sigs" }, {});
|
|
||||||
|
|
||||||
// ... and one to upload the cross-signing keys (with UIA)
|
|
||||||
fetchMock.post(
|
|
||||||
// legacy crypto uses /unstable/; /v3/ is correct
|
|
||||||
{
|
|
||||||
url: new RegExp("/_matrix/client/(unstable|v3)/keys/device_signing/upload"),
|
|
||||||
name: "upload-keys",
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create cross-signing keys, publish the keys
|
|
||||||
* Mock and bootstrap all the required steps
|
|
||||||
*
|
*
|
||||||
* @param authDict - The parameters to as the `auth` dict in the key upload request.
|
* @param authDict - The parameters to as the `auth` dict in the key upload request.
|
||||||
* @see https://spec.matrix.org/v1.6/client-server-api/#authentication-types
|
* @see https://spec.matrix.org/v1.6/client-server-api/#authentication-types
|
||||||
*/
|
*/
|
||||||
async function bootstrapCrossSigning(authDict: IAuthDict): Promise<void> {
|
async function bootstrapCrossSigning(authDict: IAuthDict): Promise<void> {
|
||||||
const uiaCallback: UIAuthCallback<void> = async (makeRequest) => {
|
|
||||||
await makeRequest(authDict);
|
|
||||||
};
|
|
||||||
|
|
||||||
// now bootstrap cross signing, and check it resolves successfully
|
|
||||||
await aliceClient.getCrypto()?.bootstrapCrossSigning({
|
await aliceClient.getCrypto()?.bootstrapCrossSigning({
|
||||||
authUploadDeviceSigningKeys: uiaCallback,
|
authUploadDeviceSigningKeys: (makeRequest) => makeRequest(authDict).then(() => undefined),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -50,8 +50,9 @@ import { ISyncResponder, SyncResponder } from "../../test-utils/SyncResponder";
|
|||||||
import { escapeRegExp } from "../../../src/utils";
|
import { escapeRegExp } from "../../../src/utils";
|
||||||
import { downloadDeviceToJsDevice } from "../../../src/rust-crypto/device-converter";
|
import { downloadDeviceToJsDevice } from "../../../src/rust-crypto/device-converter";
|
||||||
import { flushPromises } from "../../test-utils/flushPromises";
|
import { flushPromises } from "../../test-utils/flushPromises";
|
||||||
import { mockInitialApiRequests } from "../../test-utils/mockEndpoints";
|
import { mockInitialApiRequests, mockSetupCrossSigningRequests } from "../../test-utils/mockEndpoints";
|
||||||
import { SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/secret-storage";
|
import { AddSecretStorageKeyOpts, SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/secret-storage";
|
||||||
|
import { CryptoCallbacks } from "../../../src/crypto-api";
|
||||||
|
|
||||||
const ROOM_ID = "!room:id";
|
const ROOM_ID = "!room:id";
|
||||||
|
|
||||||
@@ -530,6 +531,27 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the {@link CryptoCallbacks}
|
||||||
|
*/
|
||||||
|
function createCryptoCallbacks(): CryptoCallbacks {
|
||||||
|
// Store the cached secret storage key and return it when `getSecretStorageKey` is called
|
||||||
|
let cachedKey: { keyId: string; key: Uint8Array };
|
||||||
|
const cacheSecretStorageKey = (keyId: string, keyInfo: AddSecretStorageKeyOpts, key: Uint8Array) => {
|
||||||
|
cachedKey = {
|
||||||
|
keyId,
|
||||||
|
key,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSecretStorageKey = () => Promise.resolve<[string, Uint8Array]>([cachedKey.keyId, cachedKey.key]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
cacheSecretStorageKey,
|
||||||
|
getSecretStorageKey,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(
|
beforeEach(
|
||||||
async () => {
|
async () => {
|
||||||
// anything that we don't have a specific matcher for silently returns a 404
|
// anything that we don't have a specific matcher for silently returns a 404
|
||||||
@@ -542,6 +564,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
userId: "@alice:localhost",
|
userId: "@alice:localhost",
|
||||||
accessToken: "akjgkrgjs",
|
accessToken: "akjgkrgjs",
|
||||||
deviceId: "xzcvb",
|
deviceId: "xzcvb",
|
||||||
|
cryptoCallbacks: createCryptoCallbacks(),
|
||||||
});
|
});
|
||||||
|
|
||||||
/* set up listeners for /keys/upload and /sync */
|
/* set up listeners for /keys/upload and /sync */
|
||||||
@@ -2187,16 +2210,16 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a mock to respond to the PUT request `/_matrix/client/r0/user/:userId/account_data/:type`
|
* Create a mock to respond to the PUT request `/_matrix/client/r0/user/:userId/account_data/:type(m.secret_storage.*)`
|
||||||
* Resolved when a key is uploaded (ie in `body.content.key`)
|
* Resolved when a key is uploaded (ie in `body.content.key`)
|
||||||
* https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3useruseridaccount_datatype
|
* https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3useruseridaccount_datatype
|
||||||
*/
|
*/
|
||||||
function awaitKeyStoredInAccountData(): Promise<string> {
|
function awaitSecretStorageKeyStoredInAccountData(): Promise<string> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// This url is called multiple times during the secret storage bootstrap process
|
// This url is called multiple times during the secret storage bootstrap process
|
||||||
// When we received the newly generated key, we return it
|
// When we received the newly generated key, we return it
|
||||||
fetchMock.put(
|
fetchMock.put(
|
||||||
"express:/_matrix/client/r0/user/:userId/account_data/:type",
|
"express:/_matrix/client/r0/user/:userId/account_data/:type(m.secret_storage.*)",
|
||||||
(url: string, options: RequestInit) => {
|
(url: string, options: RequestInit) => {
|
||||||
const content = JSON.parse(options.body as string);
|
const content = JSON.parse(options.body as string);
|
||||||
|
|
||||||
@@ -2211,6 +2234,25 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a mock to respond to the PUT request `/_matrix/client/r0/user/:userId/account_data/m.cross_signing.${key}`
|
||||||
|
* Resolved when the cross signing key is uploaded
|
||||||
|
* https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3useruseridaccount_datatype
|
||||||
|
*/
|
||||||
|
function awaitCrossSigningKeyUpload(key: string): Promise<Record<string, {}>> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// Called when the cross signing key is uploaded
|
||||||
|
fetchMock.put(
|
||||||
|
`express:/_matrix/client/r0/user/:userId/account_data/m.cross_signing.${key}`,
|
||||||
|
(url: string, options: RequestInit) => {
|
||||||
|
const content = JSON.parse(options.body as string);
|
||||||
|
resolve(content.encrypted);
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send in the sync response the provided `secretStorageKey` into the account_data field
|
* Send in the sync response the provided `secretStorageKey` into the account_data field
|
||||||
* The key is set for the `m.secret_storage.default_key` and `m.secret_storage.key.${secretStorageKey}` events
|
* The key is set for the `m.secret_storage.default_key` and `m.secret_storage.key.${secretStorageKey}` events
|
||||||
@@ -2249,12 +2291,14 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
await startClientAndAwaitFirstSync();
|
await startClientAndAwaitFirstSync();
|
||||||
});
|
});
|
||||||
|
|
||||||
newBackendOnly("should do no nothing if createSecretStorageKey is not set", async () => {
|
newBackendOnly(
|
||||||
await aliceClient.getCrypto()!.bootstrapSecretStorage({ setupNewSecretStorage: true });
|
"should throw an error if we are unable to create a key because createSecretStorageKey is not set",
|
||||||
|
async () => {
|
||||||
// No key was created
|
await expect(
|
||||||
expect(createSecretStorageKey).toHaveBeenCalledTimes(0);
|
aliceClient.getCrypto()!.bootstrapSecretStorage({ setupNewSecretStorage: true }),
|
||||||
});
|
).rejects.toThrow("unable to create a new secret storage key, createSecretStorageKey is not set");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
newBackendOnly("should create a new key", async () => {
|
newBackendOnly("should create a new key", async () => {
|
||||||
const bootstrapPromise = aliceClient
|
const bootstrapPromise = aliceClient
|
||||||
@@ -2262,7 +2306,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
.bootstrapSecretStorage({ setupNewSecretStorage: true, createSecretStorageKey });
|
.bootstrapSecretStorage({ setupNewSecretStorage: true, createSecretStorageKey });
|
||||||
|
|
||||||
// Wait for the key to be uploaded in the account data
|
// Wait for the key to be uploaded in the account data
|
||||||
const secretStorageKey = await awaitKeyStoredInAccountData();
|
const secretStorageKey = await awaitSecretStorageKeyStoredInAccountData();
|
||||||
|
|
||||||
// Return the newly created key in the sync response
|
// Return the newly created key in the sync response
|
||||||
sendSyncResponse(secretStorageKey);
|
sendSyncResponse(secretStorageKey);
|
||||||
@@ -2283,7 +2327,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
const bootstrapPromise = aliceClient.getCrypto()!.bootstrapSecretStorage({ createSecretStorageKey });
|
const bootstrapPromise = aliceClient.getCrypto()!.bootstrapSecretStorage({ createSecretStorageKey });
|
||||||
|
|
||||||
// Wait for the key to be uploaded in the account data
|
// Wait for the key to be uploaded in the account data
|
||||||
const secretStorageKey = await awaitKeyStoredInAccountData();
|
const secretStorageKey = await awaitSecretStorageKeyStoredInAccountData();
|
||||||
|
|
||||||
// Return the newly created key in the sync response
|
// Return the newly created key in the sync response
|
||||||
sendSyncResponse(secretStorageKey);
|
sendSyncResponse(secretStorageKey);
|
||||||
@@ -2307,7 +2351,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
.bootstrapSecretStorage({ setupNewSecretStorage: true, createSecretStorageKey });
|
.bootstrapSecretStorage({ setupNewSecretStorage: true, createSecretStorageKey });
|
||||||
|
|
||||||
// Wait for the key to be uploaded in the account data
|
// Wait for the key to be uploaded in the account data
|
||||||
let secretStorageKey = await awaitKeyStoredInAccountData();
|
let secretStorageKey = await awaitSecretStorageKeyStoredInAccountData();
|
||||||
|
|
||||||
// Return the newly created key in the sync response
|
// Return the newly created key in the sync response
|
||||||
sendSyncResponse(secretStorageKey);
|
sendSyncResponse(secretStorageKey);
|
||||||
@@ -2321,7 +2365,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
.bootstrapSecretStorage({ setupNewSecretStorage: true, createSecretStorageKey });
|
.bootstrapSecretStorage({ setupNewSecretStorage: true, createSecretStorageKey });
|
||||||
|
|
||||||
// Wait for the key to be uploaded in the account data
|
// Wait for the key to be uploaded in the account data
|
||||||
secretStorageKey = await awaitKeyStoredInAccountData();
|
secretStorageKey = await awaitSecretStorageKeyStoredInAccountData();
|
||||||
|
|
||||||
// Return the newly created key in the sync response
|
// Return the newly created key in the sync response
|
||||||
sendSyncResponse(secretStorageKey);
|
sendSyncResponse(secretStorageKey);
|
||||||
@@ -2333,5 +2377,38 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
expect(createSecretStorageKey).toHaveBeenCalledTimes(2);
|
expect(createSecretStorageKey).toHaveBeenCalledTimes(2);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
newBackendOnly("should upload cross signing keys", async () => {
|
||||||
|
mockSetupCrossSigningRequests();
|
||||||
|
|
||||||
|
// Before setting up secret-storage, bootstrap cross-signing, so that the client has cross-signing keys.
|
||||||
|
await aliceClient.getCrypto()?.bootstrapCrossSigning({});
|
||||||
|
|
||||||
|
// Now, when we bootstrap secret-storage, the cross-signing keys should be uploaded.
|
||||||
|
const bootstrapPromise = aliceClient
|
||||||
|
.getCrypto()!
|
||||||
|
.bootstrapSecretStorage({ setupNewSecretStorage: true, createSecretStorageKey });
|
||||||
|
|
||||||
|
// Wait for the key to be uploaded in the account data
|
||||||
|
const secretStorageKey = await awaitSecretStorageKeyStoredInAccountData();
|
||||||
|
|
||||||
|
// Return the newly created key in the sync response
|
||||||
|
sendSyncResponse(secretStorageKey);
|
||||||
|
|
||||||
|
// Wait for the cross signing keys to be uploaded
|
||||||
|
const [masterKey, userSigningKey, selfSigningKey] = await Promise.all([
|
||||||
|
awaitCrossSigningKeyUpload("master"),
|
||||||
|
awaitCrossSigningKeyUpload("user_signing"),
|
||||||
|
awaitCrossSigningKeyUpload("self_signing"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Finally, wait for bootstrapSecretStorage to finished
|
||||||
|
await bootstrapPromise;
|
||||||
|
|
||||||
|
// Expect the cross signing master key to be uploaded and to be encrypted with `secretStorageKey`
|
||||||
|
expect(masterKey[secretStorageKey]).toBeDefined();
|
||||||
|
expect(userSigningKey[secretStorageKey]).toBeDefined();
|
||||||
|
expect(selfSigningKey[secretStorageKey]).toBeDefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -28,3 +28,28 @@ export function mockInitialApiRequests(homeserverUrl: string) {
|
|||||||
filter_id: "fid",
|
filter_id: "fid",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock the requests needed to set up cross signing
|
||||||
|
*
|
||||||
|
* Return `{}` for `GET _matrix/client/r0/user/:userId/account_data/:type` request
|
||||||
|
* Return `{}` for `POST _matrix/client/v3/keys/signatures/upload` request (named `upload-sigs` for fetchMock check)
|
||||||
|
* Return `{}` for `POST /_matrix/client/(unstable|v3)/keys/device_signing/upload` request (named `upload-keys` for fetchMock check)
|
||||||
|
*/
|
||||||
|
export function mockSetupCrossSigningRequests(): void {
|
||||||
|
// have account_data requests return an empty object
|
||||||
|
fetchMock.get("express:/_matrix/client/r0/user/:userId/account_data/:type", {});
|
||||||
|
|
||||||
|
// we expect a request to upload signatures for our device ...
|
||||||
|
fetchMock.post({ url: "path:/_matrix/client/v3/keys/signatures/upload", name: "upload-sigs" }, {});
|
||||||
|
|
||||||
|
// ... and one to upload the cross-signing keys (with UIA)
|
||||||
|
fetchMock.post(
|
||||||
|
// legacy crypto uses /unstable/; /v3/ is correct
|
||||||
|
{
|
||||||
|
url: new RegExp("/_matrix/client/(unstable|v3)/keys/device_signing/upload"),
|
||||||
|
name: "upload-keys",
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@@ -367,6 +367,9 @@ export interface ICreateClientOpts {
|
|||||||
*/
|
*/
|
||||||
useE2eForGroupCall?: boolean;
|
useE2eForGroupCall?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crypto callbacks provided by the application
|
||||||
|
*/
|
||||||
cryptoCallbacks?: ICryptoCallbacks;
|
cryptoCallbacks?: ICryptoCallbacks;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -192,12 +192,17 @@ export interface CryptoApi {
|
|||||||
isSecretStorageReady(): Promise<boolean>;
|
isSecretStorageReady(): Promise<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bootstrap the secret storage by creating a new secret storage key and store it in the secret storage.
|
* Bootstrap the secret storage by creating a new secret storage key, add it in the secret storage and
|
||||||
|
* store the cross signing keys in the secret storage.
|
||||||
*
|
*
|
||||||
* - Do nothing if an AES key is already stored in the secret storage and `setupNewKeyBackup` is not set;
|
|
||||||
* - Generate a new key {@link GeneratedSecretStorageKey} with `createSecretStorageKey`.
|
* - Generate a new key {@link GeneratedSecretStorageKey} with `createSecretStorageKey`.
|
||||||
|
* Only if `setupNewSecretStorage` is set or if there is no AES key in the secret storage
|
||||||
* - Store this key in the secret storage and set it as the default key.
|
* - Store this key in the secret storage and set it as the default key.
|
||||||
* - Call `cryptoCallbacks.cacheSecretStorageKey` if provided.
|
* - Call `cryptoCallbacks.cacheSecretStorageKey` if provided.
|
||||||
|
* - Store the cross signing keys in the secret storage if
|
||||||
|
* - the cross signing is ready
|
||||||
|
* - a new key was created during the previous step
|
||||||
|
* - or the secret storage already contains the cross signing keys
|
||||||
*
|
*
|
||||||
* @param opts - Options object.
|
* @param opts - Options object.
|
||||||
*/
|
*/
|
||||||
|
@@ -387,42 +387,96 @@ export class RustCrypto implements CryptoBackend {
|
|||||||
createSecretStorageKey,
|
createSecretStorageKey,
|
||||||
setupNewSecretStorage,
|
setupNewSecretStorage,
|
||||||
}: CreateSecretStorageOpts = {}): Promise<void> {
|
}: CreateSecretStorageOpts = {}): Promise<void> {
|
||||||
// If createSecretStorageKey is not set, we stop
|
// If an AES Key is already stored in the secret storage and setupNewSecretStorage is not set
|
||||||
if (!createSecretStorageKey) return;
|
// we don't want to create a new key
|
||||||
|
const isNewSecretStorageKeyNeeded = setupNewSecretStorage || !(await this.secretStorageHasAESKey());
|
||||||
|
|
||||||
// See if we already have an AES secret-storage key.
|
if (isNewSecretStorageKeyNeeded) {
|
||||||
const secretStorageKeyTuple = await this.secretStorage.getKey();
|
if (!createSecretStorageKey) {
|
||||||
|
throw new Error("unable to create a new secret storage key, createSecretStorageKey is not set");
|
||||||
if (secretStorageKeyTuple) {
|
|
||||||
const [, keyInfo] = secretStorageKeyTuple;
|
|
||||||
|
|
||||||
// If an AES Key is already stored in the secret storage and setupNewSecretStorage is not set
|
|
||||||
// we don't want to create a new key
|
|
||||||
if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES && !setupNewSecretStorage) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new storage key and add it to secret storage
|
||||||
|
const recoveryKey = await createSecretStorageKey();
|
||||||
|
await this.addSecretStorageKeyToSecretStorage(recoveryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
const recoveryKey = await createSecretStorageKey();
|
const crossSigningStatus: RustSdkCryptoJs.CrossSigningStatus = await this.olmMachine.crossSigningStatus();
|
||||||
|
const hasPrivateKeys =
|
||||||
|
crossSigningStatus.hasMaster && crossSigningStatus.hasSelfSigning && crossSigningStatus.hasUserSigning;
|
||||||
|
|
||||||
|
// If we have cross-signing private keys cached, store them in secret
|
||||||
|
// storage if they are not there already.
|
||||||
|
if (
|
||||||
|
hasPrivateKeys &&
|
||||||
|
(isNewSecretStorageKeyNeeded || !(await secretStorageContainsCrossSigningKeys(this.secretStorage)))
|
||||||
|
) {
|
||||||
|
const crossSigningPrivateKeys: RustSdkCryptoJs.CrossSigningKeyExport =
|
||||||
|
await this.olmMachine.exportCrossSigningKeys();
|
||||||
|
|
||||||
|
if (!crossSigningPrivateKeys.masterKey) {
|
||||||
|
throw new Error("missing master key in cross signing private keys");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!crossSigningPrivateKeys.userSigningKey) {
|
||||||
|
throw new Error("missing user signing key in cross signing private keys");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!crossSigningPrivateKeys.self_signing_key) {
|
||||||
|
throw new Error("missing self signing key in cross signing private keys");
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.secretStorage.store("m.cross_signing.master", crossSigningPrivateKeys.masterKey);
|
||||||
|
await this.secretStorage.store("m.cross_signing.user_signing", crossSigningPrivateKeys.userSigningKey);
|
||||||
|
await this.secretStorage.store("m.cross_signing.self_signing", crossSigningPrivateKeys.self_signing_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the secretStorage key to the secret storage
|
||||||
|
* - The secret storage key must have the `keyInfo` field filled
|
||||||
|
* - The secret storage key is set as the default key of the secret storage
|
||||||
|
* - Call `cryptoCallbacks.cacheSecretStorageKey` when done
|
||||||
|
*
|
||||||
|
* @param secretStorageKey - The secret storage key to add in the secret storage.
|
||||||
|
*/
|
||||||
|
private async addSecretStorageKeyToSecretStorage(secretStorageKey: GeneratedSecretStorageKey): Promise<void> {
|
||||||
// keyInfo is required to continue
|
// keyInfo is required to continue
|
||||||
if (!recoveryKey.keyInfo) {
|
if (!secretStorageKey.keyInfo) {
|
||||||
throw new Error("missing keyInfo field in the secret storage key created by createSecretStorageKey");
|
throw new Error("missing keyInfo field in the secret storage key");
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretStorageKeyObject = await this.secretStorage.addKey(
|
const secretStorageKeyObject = await this.secretStorage.addKey(
|
||||||
SECRET_STORAGE_ALGORITHM_V1_AES,
|
SECRET_STORAGE_ALGORITHM_V1_AES,
|
||||||
recoveryKey.keyInfo,
|
secretStorageKey.keyInfo,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.secretStorage.setDefaultKeyId(secretStorageKeyObject.keyId);
|
await this.secretStorage.setDefaultKeyId(secretStorageKeyObject.keyId);
|
||||||
|
|
||||||
this.cryptoCallbacks.cacheSecretStorageKey?.(
|
this.cryptoCallbacks.cacheSecretStorageKey?.(
|
||||||
secretStorageKeyObject.keyId,
|
secretStorageKeyObject.keyId,
|
||||||
secretStorageKeyObject.keyInfo,
|
secretStorageKeyObject.keyInfo,
|
||||||
recoveryKey.privateKey,
|
secretStorageKey.privateKey,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a secret storage AES Key is already added in secret storage
|
||||||
|
*
|
||||||
|
* @returns True if an AES key is in the secret storage
|
||||||
|
*/
|
||||||
|
private async secretStorageHasAESKey(): Promise<boolean> {
|
||||||
|
// See if we already have an AES secret-storage key.
|
||||||
|
const secretStorageKeyTuple = await this.secretStorage.getKey();
|
||||||
|
|
||||||
|
if (!secretStorageKeyTuple) return false;
|
||||||
|
|
||||||
|
const [, keyInfo] = secretStorageKeyTuple;
|
||||||
|
|
||||||
|
// Check if the key is an AES key
|
||||||
|
return keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of {@link CryptoApi#getCrossSigningStatus}
|
* Implementation of {@link CryptoApi#getCrossSigningStatus}
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user