You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-23 17:02:25 +03:00
ElementR: Fix missing key check values in 4S key storage (#3950)
* fix missing key check in key storage * code review * fix tests * add recovery keys test for both backends * fix api break on GeneratedSecretStorageKey * fix test * fix test * Update src/crypto-api.ts Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update spec/unit/rust-crypto/rust-crypto.spec.ts Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update src/crypto-api.ts Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --------- Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
@@ -74,7 +74,7 @@ import {
|
|||||||
mockSetupCrossSigningRequests,
|
mockSetupCrossSigningRequests,
|
||||||
mockSetupMegolmBackupRequests,
|
mockSetupMegolmBackupRequests,
|
||||||
} from "../../test-utils/mockEndpoints";
|
} from "../../test-utils/mockEndpoints";
|
||||||
import { AddSecretStorageKeyOpts } from "../../../src/secret-storage";
|
import { SecretStorageKeyDescription } from "../../../src/secret-storage";
|
||||||
import {
|
import {
|
||||||
CrossSigningKey,
|
CrossSigningKey,
|
||||||
CryptoCallbacks,
|
CryptoCallbacks,
|
||||||
@@ -340,7 +340,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
function createCryptoCallbacks(): CryptoCallbacks {
|
function createCryptoCallbacks(): CryptoCallbacks {
|
||||||
// Store the cached secret storage key and return it when `getSecretStorageKey` is called
|
// Store the cached secret storage key and return it when `getSecretStorageKey` is called
|
||||||
let cachedKey: { keyId: string; key: Uint8Array };
|
let cachedKey: { keyId: string; key: Uint8Array };
|
||||||
const cacheSecretStorageKey = (keyId: string, keyInfo: AddSecretStorageKeyOpts, key: Uint8Array) => {
|
const cacheSecretStorageKey = (keyId: string, keyInfo: SecretStorageKeyDescription, key: Uint8Array) => {
|
||||||
cachedKey = {
|
cachedKey = {
|
||||||
keyId,
|
keyId,
|
||||||
key,
|
key,
|
||||||
@@ -2567,6 +2567,30 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
await backupStatusUpdate;
|
await backupStatusUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe("Generate 4S recovery keys", () => {
|
||||||
|
it("should create a random recovery key", async () => {
|
||||||
|
const generatedKey = await aliceClient.getCrypto()!.createRecoveryKeyFromPassphrase();
|
||||||
|
expect(generatedKey.privateKey).toBeDefined();
|
||||||
|
expect(generatedKey.privateKey).toBeInstanceOf(Uint8Array);
|
||||||
|
expect(generatedKey.privateKey.length).toBe(32);
|
||||||
|
expect(generatedKey.keyInfo?.passphrase).toBeUndefined();
|
||||||
|
expect(generatedKey.encodedPrivateKey).toBeDefined();
|
||||||
|
expect(generatedKey.encodedPrivateKey!.indexOf("Es")).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create a recovery key from passphrase", async () => {
|
||||||
|
const generatedKey = await aliceClient.getCrypto()!.createRecoveryKeyFromPassphrase("mypassphrase");
|
||||||
|
expect(generatedKey.privateKey).toBeDefined();
|
||||||
|
expect(generatedKey.privateKey).toBeInstanceOf(Uint8Array);
|
||||||
|
expect(generatedKey.privateKey.length).toBe(32);
|
||||||
|
expect(generatedKey.keyInfo?.passphrase?.algorithm).toBe("m.pbkdf2");
|
||||||
|
expect(generatedKey.keyInfo?.passphrase?.iterations).toBe(500000);
|
||||||
|
|
||||||
|
expect(generatedKey.encodedPrivateKey).toBeDefined();
|
||||||
|
expect(generatedKey.encodedPrivateKey!.indexOf("Es")).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("bootstrapSecretStorage", () => {
|
describe("bootstrapSecretStorage", () => {
|
||||||
// Doesn't work with legacy crypto, which will try to bootstrap even without private key, which is buggy.
|
// Doesn't work with legacy crypto, which will try to bootstrap even without private key, which is buggy.
|
||||||
newBackendOnly(
|
newBackendOnly(
|
||||||
@@ -2592,6 +2616,14 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
// 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 awaitSecretStorageKeyStoredInAccountData();
|
const secretStorageKey = await awaitSecretStorageKeyStoredInAccountData();
|
||||||
|
|
||||||
|
// check that the key content contains the key check info
|
||||||
|
const keyContent = accountDataAccumulator.accountDataEvents.get(
|
||||||
|
`m.secret_storage.key.${secretStorageKey}`,
|
||||||
|
)!;
|
||||||
|
// In order to verify if the key is valid, a zero secret is encrypted with the key
|
||||||
|
expect(keyContent.iv).toBeDefined();
|
||||||
|
expect(keyContent.mac).toBeDefined();
|
||||||
|
|
||||||
// Return the newly created key in the sync response
|
// Return the newly created key in the sync response
|
||||||
accountDataAccumulator.sendSyncResponseWithUpdatedAccountData(syncResponder);
|
accountDataAccumulator.sendSyncResponseWithUpdatedAccountData(syncResponder);
|
||||||
|
|
||||||
|
|||||||
@@ -33,12 +33,10 @@ export async function resetCrossSigningKeys(
|
|||||||
|
|
||||||
export async function createSecretStorageKey(): Promise<IRecoveryKey> {
|
export async function createSecretStorageKey(): Promise<IRecoveryKey> {
|
||||||
const decryption = new global.Olm.PkDecryption();
|
const decryption = new global.Olm.PkDecryption();
|
||||||
const storagePublicKey = decryption.generate_key();
|
decryption.generate_key();
|
||||||
const storagePrivateKey = decryption.get_private_key();
|
const storagePrivateKey = decryption.get_private_key();
|
||||||
decryption.free();
|
decryption.free();
|
||||||
return {
|
return {
|
||||||
// `pubkey` not used anymore with symmetric 4S
|
|
||||||
keyInfo: { pubkey: storagePublicKey, key: undefined! },
|
|
||||||
privateKey: storagePrivateKey,
|
privateKey: storagePrivateKey,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,10 +190,7 @@ describe("Secrets", function () {
|
|||||||
};
|
};
|
||||||
resetCrossSigningKeys(alice);
|
resetCrossSigningKeys(alice);
|
||||||
|
|
||||||
const { keyId: newKeyId } = await alice.addSecretStorageKey(SECRET_STORAGE_ALGORITHM_V1_AES, {
|
const { keyId: newKeyId } = await alice.addSecretStorageKey(SECRET_STORAGE_ALGORITHM_V1_AES, { key });
|
||||||
pubkey: undefined,
|
|
||||||
key: undefined,
|
|
||||||
});
|
|
||||||
// we don't await on this because it waits for the event to come down the sync
|
// we don't await on this because it waits for the event to come down the sync
|
||||||
// which won't happen in the test setup
|
// which won't happen in the test setup
|
||||||
alice.setDefaultSecretStorageKeyId(newKeyId);
|
alice.setDefaultSecretStorageKeyId(newKeyId);
|
||||||
@@ -335,7 +332,6 @@ describe("Secrets", function () {
|
|||||||
|
|
||||||
it("bootstraps when cross-signing keys in secret storage", async function () {
|
it("bootstraps when cross-signing keys in secret storage", async function () {
|
||||||
const decryption = new global.Olm.PkDecryption();
|
const decryption = new global.Olm.PkDecryption();
|
||||||
const storagePublicKey = decryption.generate_key();
|
|
||||||
const storagePrivateKey = decryption.get_private_key();
|
const storagePrivateKey = decryption.get_private_key();
|
||||||
|
|
||||||
const bob: MatrixClient = await makeTestClient(
|
const bob: MatrixClient = await makeTestClient(
|
||||||
@@ -378,8 +374,6 @@ describe("Secrets", function () {
|
|||||||
});
|
});
|
||||||
await bob.bootstrapSecretStorage({
|
await bob.bootstrapSecretStorage({
|
||||||
createSecretStorageKey: async () => ({
|
createSecretStorageKey: async () => ({
|
||||||
// `pubkey` not used anymore with symmetric 4S
|
|
||||||
keyInfo: { pubkey: storagePublicKey },
|
|
||||||
privateKey: storagePrivateKey,
|
privateKey: storagePrivateKey,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -739,8 +739,8 @@ describe("RustCrypto", () => {
|
|||||||
// Expect the private key to be an Uint8Array with a length of 32
|
// Expect the private key to be an Uint8Array with a length of 32
|
||||||
expect(recoveryKey.privateKey).toBeInstanceOf(Uint8Array);
|
expect(recoveryKey.privateKey).toBeInstanceOf(Uint8Array);
|
||||||
expect(recoveryKey.privateKey.length).toBe(32);
|
expect(recoveryKey.privateKey.length).toBe(32);
|
||||||
// Expect keyInfo to be empty
|
// Expect passphrase info to be absent
|
||||||
expect(Object.keys(recoveryKey.keyInfo!).length).toBe(0);
|
expect(recoveryKey.keyInfo?.passphrase).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create a recovery key with password", async () => {
|
it("should create a recovery key with password", async () => {
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ describe("ServerSideSecretStorageImpl", function () {
|
|||||||
it("should allow storing a default key", async function () {
|
it("should allow storing a default key", async function () {
|
||||||
const accountDataAdapter = mockAccountDataClient();
|
const accountDataAdapter = mockAccountDataClient();
|
||||||
const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, {});
|
const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, {});
|
||||||
const result = await secretStorage.addKey("m.secret_storage.v1.aes-hmac-sha2");
|
const result = await secretStorage.addKey("m.secret_storage.v1.aes-hmac-sha2", {
|
||||||
|
key: new Uint8Array(32),
|
||||||
|
});
|
||||||
|
|
||||||
// it should have made up a 32-character key id
|
// it should have made up a 32-character key id
|
||||||
expect(result.keyId.length).toEqual(32);
|
expect(result.keyId.length).toEqual(32);
|
||||||
@@ -46,7 +48,13 @@ describe("ServerSideSecretStorageImpl", function () {
|
|||||||
it("should allow storing a key with an explicit id", async function () {
|
it("should allow storing a key with an explicit id", async function () {
|
||||||
const accountDataAdapter = mockAccountDataClient();
|
const accountDataAdapter = mockAccountDataClient();
|
||||||
const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, {});
|
const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, {});
|
||||||
const result = await secretStorage.addKey("m.secret_storage.v1.aes-hmac-sha2", {}, "myKeyId");
|
const result = await secretStorage.addKey(
|
||||||
|
"m.secret_storage.v1.aes-hmac-sha2",
|
||||||
|
{
|
||||||
|
key: new Uint8Array(32),
|
||||||
|
},
|
||||||
|
"myKeyId",
|
||||||
|
);
|
||||||
|
|
||||||
// it should have made up a 32-character key id
|
// it should have made up a 32-character key id
|
||||||
expect(result.keyId).toEqual("myKeyId");
|
expect(result.keyId).toEqual("myKeyId");
|
||||||
@@ -59,7 +67,10 @@ describe("ServerSideSecretStorageImpl", function () {
|
|||||||
it("should allow storing a key with a name", async function () {
|
it("should allow storing a key with a name", async function () {
|
||||||
const accountDataAdapter = mockAccountDataClient();
|
const accountDataAdapter = mockAccountDataClient();
|
||||||
const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, {});
|
const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, {});
|
||||||
const result = await secretStorage.addKey("m.secret_storage.v1.aes-hmac-sha2", { name: "mykey" });
|
const result = await secretStorage.addKey("m.secret_storage.v1.aes-hmac-sha2", {
|
||||||
|
name: "mykey",
|
||||||
|
key: new Uint8Array(32),
|
||||||
|
});
|
||||||
|
|
||||||
expect(result.keyInfo.name).toEqual("mykey");
|
expect(result.keyInfo.name).toEqual("mykey");
|
||||||
|
|
||||||
@@ -80,6 +91,7 @@ describe("ServerSideSecretStorageImpl", function () {
|
|||||||
};
|
};
|
||||||
const result = await secretStorage.addKey("m.secret_storage.v1.aes-hmac-sha2", {
|
const result = await secretStorage.addKey("m.secret_storage.v1.aes-hmac-sha2", {
|
||||||
passphrase,
|
passphrase,
|
||||||
|
key: new Uint8Array(32),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.keyInfo.passphrase).toEqual(passphrase);
|
expect(result.keyInfo.passphrase).toEqual(passphrase);
|
||||||
@@ -93,7 +105,9 @@ describe("ServerSideSecretStorageImpl", function () {
|
|||||||
it("should complain about invalid algorithm", async function () {
|
it("should complain about invalid algorithm", async function () {
|
||||||
const accountDataAdapter = mockAccountDataClient();
|
const accountDataAdapter = mockAccountDataClient();
|
||||||
const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, {});
|
const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, {});
|
||||||
await expect(() => secretStorage.addKey("bad_alg")).rejects.toThrow("Unknown key algorithm");
|
await expect(() => secretStorage.addKey("bad_alg", { key: new Uint8Array(32) })).rejects.toThrow(
|
||||||
|
"Unknown key algorithm",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import type { IMegolmSessionData } from "./@types/crypto";
|
|||||||
import { Room } from "./models/room";
|
import { Room } from "./models/room";
|
||||||
import { DeviceMap } from "./models/device";
|
import { DeviceMap } from "./models/device";
|
||||||
import { UIAuthCallback } from "./interactive-auth";
|
import { UIAuthCallback } from "./interactive-auth";
|
||||||
import { AddSecretStorageKeyOpts, SecretStorageCallbacks, SecretStorageKeyDescription } from "./secret-storage";
|
import { PassphraseInfo, SecretStorageCallbacks, SecretStorageKeyDescription } from "./secret-storage";
|
||||||
import { VerificationRequest } from "./crypto-api/verification";
|
import { VerificationRequest } from "./crypto-api/verification";
|
||||||
import { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "./crypto-api/keybackup";
|
import { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "./crypto-api/keybackup";
|
||||||
import { ISignatures } from "./@types/signed";
|
import { ISignatures } from "./@types/signed";
|
||||||
@@ -710,10 +710,15 @@ export interface CrossSigningKeyInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recovery key created by {@link CryptoApi#createRecoveryKeyFromPassphrase}
|
* Recovery key created by {@link CryptoApi#createRecoveryKeyFromPassphrase} or {@link CreateSecretStorageOpts#createSecretStorageKey}.
|
||||||
*/
|
*/
|
||||||
export interface GeneratedSecretStorageKey {
|
export interface GeneratedSecretStorageKey {
|
||||||
keyInfo?: AddSecretStorageKeyOpts;
|
keyInfo?: {
|
||||||
|
/** If the key was derived from a passphrase, information (algorithm, salt, etc) on that derivation. */
|
||||||
|
passphrase?: PassphraseInfo;
|
||||||
|
/** Optional human-readable name for the key, to be stored in account_data. */
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
/** The raw generated private key. */
|
/** The raw generated private key. */
|
||||||
privateKey: Uint8Array;
|
privateKey: Uint8Array;
|
||||||
/** The generated key, encoded for display to the user per https://spec.matrix.org/v1.7/client-server-api/#key-representation. */
|
/** The generated key, encoded for display to the user per https://spec.matrix.org/v1.7/client-server-api/#key-representation. */
|
||||||
|
|||||||
@@ -73,11 +73,7 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> im
|
|||||||
/**
|
/**
|
||||||
* Add a key for encrypting secrets.
|
* Add a key for encrypting secrets.
|
||||||
*/
|
*/
|
||||||
public addKey(
|
public addKey(algorithm: string, opts: AddSecretStorageKeyOpts, keyId?: string): Promise<SecretStorageKeyObject> {
|
||||||
algorithm: string,
|
|
||||||
opts: AddSecretStorageKeyOpts = {},
|
|
||||||
keyId?: string,
|
|
||||||
): Promise<SecretStorageKeyObject> {
|
|
||||||
return this.storageImpl.addKey(algorithm, opts, keyId);
|
return this.storageImpl.addKey(algorithm, opts, keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -708,25 +708,30 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
|||||||
public async createRecoveryKeyFromPassphrase(password?: string): Promise<IRecoveryKey> {
|
public async createRecoveryKeyFromPassphrase(password?: string): Promise<IRecoveryKey> {
|
||||||
const decryption = new global.Olm.PkDecryption();
|
const decryption = new global.Olm.PkDecryption();
|
||||||
try {
|
try {
|
||||||
const keyInfo: Partial<IRecoveryKey["keyInfo"]> = {};
|
|
||||||
if (password) {
|
if (password) {
|
||||||
const derivation = await keyFromPassphrase(password);
|
const derivation = await keyFromPassphrase(password);
|
||||||
keyInfo.passphrase = {
|
|
||||||
|
decryption.init_with_private_key(derivation.key);
|
||||||
|
const privateKey = decryption.get_private_key();
|
||||||
|
return {
|
||||||
|
keyInfo: {
|
||||||
|
passphrase: {
|
||||||
algorithm: "m.pbkdf2",
|
algorithm: "m.pbkdf2",
|
||||||
iterations: derivation.iterations,
|
iterations: derivation.iterations,
|
||||||
salt: derivation.salt,
|
salt: derivation.salt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
privateKey: privateKey,
|
||||||
|
encodedPrivateKey: encodeRecoveryKey(privateKey),
|
||||||
};
|
};
|
||||||
keyInfo.pubkey = decryption.init_with_private_key(derivation.key);
|
|
||||||
} else {
|
} else {
|
||||||
keyInfo.pubkey = decryption.generate_key();
|
decryption.generate_key();
|
||||||
}
|
|
||||||
const privateKey = decryption.get_private_key();
|
const privateKey = decryption.get_private_key();
|
||||||
const encodedPrivateKey = encodeRecoveryKey(privateKey);
|
|
||||||
return {
|
return {
|
||||||
keyInfo: keyInfo as IRecoveryKey["keyInfo"],
|
privateKey: privateKey,
|
||||||
encodedPrivateKey,
|
encodedPrivateKey: encodeRecoveryKey(privateKey),
|
||||||
privateKey,
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
decryption?.free();
|
decryption?.free();
|
||||||
}
|
}
|
||||||
@@ -986,17 +991,11 @@ 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: AddSecretStorageKeyOpts, privateKey?: Uint8Array): Promise<string> => {
|
const createSSSS = async (opts: AddSecretStorageKeyOpts): Promise<string> => {
|
||||||
if (privateKey) {
|
|
||||||
opts.key = privateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { keyId, keyInfo } = await secretStorage.addKey(SECRET_STORAGE_ALGORITHM_V1_AES, opts);
|
const { keyId, keyInfo } = await secretStorage.addKey(SECRET_STORAGE_ALGORITHM_V1_AES, opts);
|
||||||
|
|
||||||
if (privateKey) {
|
|
||||||
// make the private key available to encrypt 4S secrets
|
// make the private key available to encrypt 4S secrets
|
||||||
builder.ssssCryptoCallbacks.addPrivateKey(keyId, keyInfo, privateKey);
|
builder.ssssCryptoCallbacks.addPrivateKey(keyId, keyInfo, opts.key);
|
||||||
}
|
|
||||||
|
|
||||||
await secretStorage.setDefaultKeyId(keyId);
|
await secretStorage.setDefaultKeyId(keyId);
|
||||||
return keyId;
|
return keyId;
|
||||||
@@ -1060,8 +1059,8 @@ 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 AddSecretStorageKeyOpts, privateKey } = await createSecretStorageKey();
|
const { keyInfo, privateKey } = await createSecretStorageKey();
|
||||||
newKeyId = await createSSSS(keyInfo, privateKey);
|
newKeyId = await createSSSS({ passphrase: keyInfo?.passphrase, key: privateKey, name: keyInfo?.name });
|
||||||
} else if (!storageExists && keyBackupInfo) {
|
} else if (!storageExists && keyBackupInfo) {
|
||||||
// we have an existing backup, but no SSSS
|
// we have an existing backup, but no SSSS
|
||||||
logger.log("Secret storage does not exist, using key backup key");
|
logger.log("Secret storage does not exist, using key backup key");
|
||||||
@@ -1071,7 +1070,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 AddSecretStorageKeyOpts;
|
const opts = { key: backupKey } 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: ???
|
||||||
@@ -1083,7 +1082,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
newKeyId = await createSSSS(opts, backupKey);
|
newKeyId = await createSSSS(opts);
|
||||||
|
|
||||||
// store the backup key in secret storage
|
// store the backup key in secret storage
|
||||||
await secretStorage.store("m.megolm_backup.v1", encodeBase64(backupKey!), [newKeyId]);
|
await secretStorage.store("m.megolm_backup.v1", encodeBase64(backupKey!), [newKeyId]);
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ import {
|
|||||||
import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter";
|
import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter";
|
||||||
import { IDownloadKeyResult, IQueryKeysRequest } from "../client";
|
import { IDownloadKeyResult, IQueryKeysRequest } from "../client";
|
||||||
import { Device, DeviceMap } from "../models/device";
|
import { Device, DeviceMap } from "../models/device";
|
||||||
import { AddSecretStorageKeyOpts, SECRET_STORAGE_ALGORITHM_V1_AES, ServerSideSecretStorage } from "../secret-storage";
|
import { SECRET_STORAGE_ALGORITHM_V1_AES, ServerSideSecretStorage } from "../secret-storage";
|
||||||
import { CrossSigningIdentity } from "./CrossSigningIdentity";
|
import { CrossSigningIdentity } from "./CrossSigningIdentity";
|
||||||
import { secretStorageCanAccessSecrets, secretStorageContainsCrossSigningKeys } from "./secret-storage";
|
import { secretStorageCanAccessSecrets, secretStorageContainsCrossSigningKeys } from "./secret-storage";
|
||||||
import { keyFromPassphrase } from "../crypto/key_passphrase";
|
import { keyFromPassphrase } from "../crypto/key_passphrase";
|
||||||
@@ -748,15 +748,11 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
|
|||||||
* @param secretStorageKey - The secret storage key to add in the secret storage.
|
* @param secretStorageKey - The secret storage key to add in the secret storage.
|
||||||
*/
|
*/
|
||||||
private async addSecretStorageKeyToSecretStorage(secretStorageKey: GeneratedSecretStorageKey): Promise<void> {
|
private async addSecretStorageKeyToSecretStorage(secretStorageKey: GeneratedSecretStorageKey): Promise<void> {
|
||||||
// keyInfo is required to continue
|
const secretStorageKeyObject = await this.secretStorage.addKey(SECRET_STORAGE_ALGORITHM_V1_AES, {
|
||||||
if (!secretStorageKey.keyInfo) {
|
passphrase: secretStorageKey.keyInfo?.passphrase,
|
||||||
throw new Error("missing keyInfo field in the secret storage key");
|
name: secretStorageKey.keyInfo?.name,
|
||||||
}
|
key: secretStorageKey.privateKey,
|
||||||
|
});
|
||||||
const secretStorageKeyObject = await this.secretStorage.addKey(
|
|
||||||
SECRET_STORAGE_ALGORITHM_V1_AES,
|
|
||||||
secretStorageKey.keyInfo,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.secretStorage.setDefaultKeyId(secretStorageKeyObject.keyId);
|
await this.secretStorage.setDefaultKeyId(secretStorageKeyObject.keyId);
|
||||||
|
|
||||||
@@ -817,31 +813,30 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
|
|||||||
* Implementation of {@link CryptoApi#createRecoveryKeyFromPassphrase}
|
* Implementation of {@link CryptoApi#createRecoveryKeyFromPassphrase}
|
||||||
*/
|
*/
|
||||||
public async createRecoveryKeyFromPassphrase(password?: string): Promise<GeneratedSecretStorageKey> {
|
public async createRecoveryKeyFromPassphrase(password?: string): Promise<GeneratedSecretStorageKey> {
|
||||||
let key: Uint8Array;
|
|
||||||
|
|
||||||
const keyInfo: AddSecretStorageKeyOpts = {};
|
|
||||||
if (password) {
|
if (password) {
|
||||||
// Generate the key from the passphrase
|
// Generate the key from the passphrase
|
||||||
const derivation = await keyFromPassphrase(password);
|
const derivation = await keyFromPassphrase(password);
|
||||||
keyInfo.passphrase = {
|
return {
|
||||||
|
keyInfo: {
|
||||||
|
passphrase: {
|
||||||
algorithm: "m.pbkdf2",
|
algorithm: "m.pbkdf2",
|
||||||
iterations: derivation.iterations,
|
iterations: derivation.iterations,
|
||||||
salt: derivation.salt,
|
salt: derivation.salt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
privateKey: derivation.key,
|
||||||
|
encodedPrivateKey: encodeRecoveryKey(derivation.key),
|
||||||
};
|
};
|
||||||
key = derivation.key;
|
|
||||||
} else {
|
} else {
|
||||||
// Using the navigator crypto API to generate the private key
|
// Using the navigator crypto API to generate the private key
|
||||||
key = new Uint8Array(32);
|
const key = new Uint8Array(32);
|
||||||
crypto.getRandomValues(key);
|
crypto.getRandomValues(key);
|
||||||
}
|
|
||||||
|
|
||||||
const encodedPrivateKey = encodeRecoveryKey(key);
|
|
||||||
return {
|
return {
|
||||||
keyInfo,
|
|
||||||
encodedPrivateKey,
|
|
||||||
privateKey: key,
|
privateKey: key,
|
||||||
|
encodedPrivateKey: encodeRecoveryKey(key),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of {@link CryptoApi.getEncryptionInfoForEvent}.
|
* Implementation of {@link CryptoApi.getEncryptionInfoForEvent}.
|
||||||
|
|||||||
@@ -100,10 +100,12 @@ export interface PassphraseInfo {
|
|||||||
* Options for {@link ServerSideSecretStorageImpl#addKey}.
|
* Options for {@link ServerSideSecretStorageImpl#addKey}.
|
||||||
*/
|
*/
|
||||||
export interface AddSecretStorageKeyOpts {
|
export interface AddSecretStorageKeyOpts {
|
||||||
pubkey?: string;
|
/** Information for deriving the key from a passphrase if any. */
|
||||||
passphrase?: PassphraseInfo;
|
passphrase?: PassphraseInfo;
|
||||||
|
/** Optional name of the key. */
|
||||||
name?: string;
|
name?: string;
|
||||||
key?: Uint8Array;
|
/** The private key. Will be used to generate the key check values in the key info; it will not be stored on the server */
|
||||||
|
key: Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -380,7 +382,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage {
|
|||||||
*/
|
*/
|
||||||
public async addKey(
|
public async addKey(
|
||||||
algorithm: string,
|
algorithm: string,
|
||||||
opts: AddSecretStorageKeyOpts = {},
|
opts: AddSecretStorageKeyOpts,
|
||||||
keyId?: string,
|
keyId?: string,
|
||||||
): Promise<SecretStorageKeyObject> {
|
): Promise<SecretStorageKeyObject> {
|
||||||
if (algorithm !== SECRET_STORAGE_ALGORITHM_V1_AES) {
|
if (algorithm !== SECRET_STORAGE_ALGORITHM_V1_AES) {
|
||||||
@@ -396,11 +398,10 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage {
|
|||||||
if (opts.passphrase) {
|
if (opts.passphrase) {
|
||||||
keyInfo.passphrase = opts.passphrase;
|
keyInfo.passphrase = opts.passphrase;
|
||||||
}
|
}
|
||||||
if (opts.key) {
|
|
||||||
const { iv, mac } = await calculateKeyCheck(opts.key);
|
const { iv, mac } = await calculateKeyCheck(opts.key);
|
||||||
keyInfo.iv = iv;
|
keyInfo.iv = iv;
|
||||||
keyInfo.mac = mac;
|
keyInfo.mac = mac;
|
||||||
}
|
|
||||||
|
|
||||||
// Create a unique key id. XXX: this is racey.
|
// Create a unique key id. XXX: this is racey.
|
||||||
if (!keyId) {
|
if (!keyId) {
|
||||||
|
|||||||
Reference in New Issue
Block a user