From b917be214fbb42522736d33d2b2f194198ee064f Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 13 Sep 2024 09:51:00 +0200 Subject: [PATCH 01/12] Move `crypto/key_passphrase.ts` to `crypto-api/key-passphrase.ts` --- src/client.ts | 9 ++++-- src/crypto-api/index.ts | 1 + .../key-passphrase.ts} | 28 +++++++++---------- src/crypto/backup.ts | 2 +- src/crypto/index.ts | 2 +- src/rust-crypto/rust-crypto.ts | 2 +- 6 files changed, 25 insertions(+), 19 deletions(-) rename src/{crypto/key_passphrase.ts => crypto-api/key-passphrase.ts} (75%) diff --git a/src/client.ts b/src/client.ts index ca506caa042..5f846d36083 100644 --- a/src/client.ts +++ b/src/client.ts @@ -86,7 +86,6 @@ import { } from "./crypto/index.ts"; import { DeviceInfo } from "./crypto/deviceinfo.ts"; import { decodeRecoveryKey } from "./crypto/recoverykey.ts"; -import { keyFromAuthData } from "./crypto/key_passphrase.ts"; import { User, UserEvent, UserEventHandlerMap } from "./models/user.ts"; import { getHttpUriForMxc } from "./content-repo.ts"; import { SearchResult } from "./models/search-result.ts"; @@ -223,7 +222,13 @@ import { LocalNotificationSettings } from "./@types/local_notifications.ts"; import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature.ts"; import { BackupDecryptor, CryptoBackend } from "./common-crypto/CryptoBackend.ts"; import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants.ts"; -import { BootstrapCrossSigningOpts, CrossSigningKeyInfo, CryptoApi, ImportRoomKeysOpts } from "./crypto-api/index.ts"; +import { + BootstrapCrossSigningOpts, + CrossSigningKeyInfo, + CryptoApi, + ImportRoomKeysOpts, + keyFromAuthData, +} from "./crypto-api/index.ts"; import { DeviceInfoMap } from "./crypto/DeviceList.ts"; import { AddSecretStorageKeyOpts, diff --git a/src/crypto-api/index.ts b/src/crypto-api/index.ts index 6b9db3811dc..134e37989be 100644 --- a/src/crypto-api/index.ts +++ b/src/crypto-api/index.ts @@ -967,3 +967,4 @@ export interface OwnDeviceKeys { export * from "./verification.ts"; export * from "./keybackup.ts"; +export * from "./key-passphrase.ts"; diff --git a/src/crypto/key_passphrase.ts b/src/crypto-api/key-passphrase.ts similarity index 75% rename from src/crypto/key_passphrase.ts rename to src/crypto-api/key-passphrase.ts index 85b9c48fbcd..e8cae0c3850 100644 --- a/src/crypto/key_passphrase.ts +++ b/src/crypto-api/key-passphrase.ts @@ -1,18 +1,18 @@ /* -Copyright 2018 - 2021 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Copyright 2024 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { randomString } from "../randomstring.ts"; diff --git a/src/crypto/backup.ts b/src/crypto/backup.ts index 3ac6a3ddc0b..4b96ad7d382 100644 --- a/src/crypto/backup.ts +++ b/src/crypto/backup.ts @@ -24,7 +24,6 @@ import { logger } from "../logger.ts"; import { MEGOLM_ALGORITHM, verifySignature } from "./olmlib.ts"; import { DeviceInfo } from "./deviceinfo.ts"; import { DeviceTrustLevel } from "./CrossSigning.ts"; -import { keyFromPassphrase } from "./key_passphrase.ts"; import { encodeUri, safeSet, sleep } from "../utils.ts"; import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store.ts"; import { encodeRecoveryKey } from "./recoverykey.ts"; @@ -41,6 +40,7 @@ import { CryptoEvent } from "./index.ts"; import { ClientPrefix, HTTPError, MatrixError, Method } from "../http-api/index.ts"; import { BackupTrustInfo } from "../crypto-api/keybackup.ts"; import { BackupDecryptor } from "../common-crypto/CryptoBackend.ts"; +import { keyFromPassphrase } from "../crypto-api/index.ts"; const KEY_BACKUP_KEYS_PER_REQUEST = 200; const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 420160c0de4..ea1eb4d7c0a 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -41,7 +41,6 @@ import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store.ts"; import { VerificationBase } from "./verification/Base.ts"; import { ReciprocateQRCode, SCAN_QR_CODE_METHOD, SHOW_QR_CODE_METHOD } from "./verification/QRCode.ts"; import { SAS as SASVerification } from "./verification/SAS.ts"; -import { keyFromPassphrase } from "./key_passphrase.ts"; import { decodeRecoveryKey, encodeRecoveryKey } from "./recoverykey.ts"; import { VerificationRequest } from "./verification/request/VerificationRequest.ts"; import { InRoomChannel, InRoomRequests } from "./verification/request/InRoomChannel.ts"; @@ -97,6 +96,7 @@ import { ImportRoomKeysOpts, KeyBackupCheck, KeyBackupInfo, + keyFromPassphrase, OwnDeviceKeys, VerificationRequest as CryptoApiVerificationRequest, } from "../crypto-api/index.ts"; diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index c43b3911c26..bdab1ea8573 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -55,6 +55,7 @@ import { ImportRoomKeysOpts, KeyBackupCheck, KeyBackupInfo, + keyFromPassphrase, OwnDeviceKeys, UserVerificationStatus, VerificationRequest, @@ -65,7 +66,6 @@ import { Device, DeviceMap } from "../models/device.ts"; import { SECRET_STORAGE_ALGORITHM_V1_AES, ServerSideSecretStorage } from "../secret-storage.ts"; import { CrossSigningIdentity } from "./CrossSigningIdentity.ts"; import { secretStorageCanAccessSecrets, secretStorageContainsCrossSigningKeys } from "./secret-storage.ts"; -import { keyFromPassphrase } from "../crypto/key_passphrase.ts"; import { encodeRecoveryKey } from "../crypto/recoverykey.ts"; import { isVerificationEvent, RustVerificationRequest, verificationMethodIdentifierToMethod } from "./verification.ts"; import { EventType, MsgType } from "../@types/event.ts"; From af7243c4a7c08ffecccf7f4d08e5e29ba4595e2d Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 13 Sep 2024 10:46:21 +0200 Subject: [PATCH 02/12] Re-export `crypto-api/key-passphrase` into `crypto/key_passphrase.ts` --- src/crypto/key_passphrase.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/crypto/key_passphrase.ts diff --git a/src/crypto/key_passphrase.ts b/src/crypto/key_passphrase.ts new file mode 100644 index 00000000000..683066abdcb --- /dev/null +++ b/src/crypto/key_passphrase.ts @@ -0,0 +1,18 @@ +/* +Copyright 2018 - 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Re-export the key passphrase functions to avoid breaking changes +export * from "../crypto-api/key-passphrase.ts"; From 8ebb287b3dadb4b4bc5b7320a643045b04c826c9 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 13 Sep 2024 11:00:38 +0200 Subject: [PATCH 03/12] Add doc --- src/crypto-api/key-passphrase.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/crypto-api/key-passphrase.ts b/src/crypto-api/key-passphrase.ts index e8cae0c3850..1650a418a77 100644 --- a/src/crypto-api/key-passphrase.ts +++ b/src/crypto-api/key-passphrase.ts @@ -34,6 +34,11 @@ interface IKey { iterations: number; } +/** + * Derive a key from a passphrase using the salt and iterations from the auth data. + * @param authData + * @param password + */ export function keyFromAuthData(authData: IAuthData, password: string): Promise { if (!authData.private_key_salt || !authData.private_key_iterations) { throw new Error("Salt and/or iterations not found: " + "this backup cannot be restored with a passphrase"); @@ -47,6 +52,10 @@ export function keyFromAuthData(authData: IAuthData, password: string): Promise< ); } +/** + * Derive a key from a passphrase. + * @param password + */ export async function keyFromPassphrase(password: string): Promise { const salt = randomString(32); @@ -55,6 +64,13 @@ export async function keyFromPassphrase(password: string): Promise { return { key, salt, iterations: DEFAULT_ITERATIONS }; } +/** + * Derive a key from a passphrase using PBKDF2. + * @param password + * @param salt + * @param iterations + * @param numBits + */ export async function deriveKey( password: string, salt: string, From e1cc2d32b49b9934a8682060d2ec6661883ef609 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 13 Sep 2024 11:19:21 +0200 Subject: [PATCH 04/12] Deprecate `MatrixClient.keyBackupKeyFromPassword` --- src/client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client.ts b/src/client.ts index 5f846d36083..79dfd3dca07 100644 --- a/src/client.ts +++ b/src/client.ts @@ -3650,6 +3650,7 @@ export class MatrixClient extends TypedEventEmitter { return keyFromAuthData(backupInfo.auth_data, password); From af26615946a9e327f233797d1437b36122a65069 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 13 Sep 2024 16:53:02 +0200 Subject: [PATCH 05/12] Move `keyFromAuthData` to `common-crypto/key-passphrase.ts` --- src/common-crypto/key-passphrase.ts | 44 +++++++++++++++++++++++++++++ src/crypto-api/key-passphrase.ts | 24 ---------------- 2 files changed, 44 insertions(+), 24 deletions(-) create mode 100644 src/common-crypto/key-passphrase.ts diff --git a/src/common-crypto/key-passphrase.ts b/src/common-crypto/key-passphrase.ts new file mode 100644 index 00000000000..f81286a5af3 --- /dev/null +++ b/src/common-crypto/key-passphrase.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2024 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { deriveKey } from "../crypto-api/index.ts"; + +const DEFAULT_BIT_SIZE = 256; + +/* eslint-disable camelcase */ +interface IAuthData { + private_key_salt?: string; + private_key_iterations?: number; + private_key_bits?: number; +} + +/** + * Derive a backup key from a passphrase using the salt and iterations from the auth data. + * @param authData - The auth data containing the salt and iterations + * @param passphrase - The passphrase to derive the key from + */ +export function keyFromAuthData(authData: IAuthData, passphrase: string): Promise { + if (!authData.private_key_salt || !authData.private_key_iterations) { + throw new Error("Salt and/or iterations not found: " + "this backup cannot be restored with a passphrase"); + } + + return deriveKey( + passphrase, + authData.private_key_salt, + authData.private_key_iterations, + authData.private_key_bits || DEFAULT_BIT_SIZE, + ); +} diff --git a/src/crypto-api/key-passphrase.ts b/src/crypto-api/key-passphrase.ts index 1650a418a77..9c31a6189d5 100644 --- a/src/crypto-api/key-passphrase.ts +++ b/src/crypto-api/key-passphrase.ts @@ -20,12 +20,6 @@ const DEFAULT_ITERATIONS = 500000; const DEFAULT_BITSIZE = 256; -/* eslint-disable camelcase */ -interface IAuthData { - private_key_salt?: string; - private_key_iterations?: number; - private_key_bits?: number; -} /* eslint-enable camelcase */ interface IKey { @@ -34,24 +28,6 @@ interface IKey { iterations: number; } -/** - * Derive a key from a passphrase using the salt and iterations from the auth data. - * @param authData - * @param password - */ -export function keyFromAuthData(authData: IAuthData, password: string): Promise { - if (!authData.private_key_salt || !authData.private_key_iterations) { - throw new Error("Salt and/or iterations not found: " + "this backup cannot be restored with a passphrase"); - } - - return deriveKey( - password, - authData.private_key_salt, - authData.private_key_iterations, - authData.private_key_bits || DEFAULT_BITSIZE, - ); -} - /** * Derive a key from a passphrase. * @param password From 1c05400ec01db155d13297da8851d27dc8545d3f Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 13 Sep 2024 17:41:00 +0200 Subject: [PATCH 06/12] Fix faulty import --- src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index a69cbb2430c..c7056c42695 100644 --- a/src/client.ts +++ b/src/client.ts @@ -227,7 +227,6 @@ import { CryptoApi, decodeRecoveryKey, ImportRoomKeysOpts, - keyFromAuthData, } from "./crypto-api/index.ts"; import { DeviceInfoMap } from "./crypto/DeviceList.ts"; import { @@ -244,6 +243,7 @@ import { RoomMessageEventContent, StickerEventContent } from "./@types/events.ts import { ImageInfo } from "./@types/media.ts"; import { Capabilities, ServerCapabilities } from "./serverCapabilities.ts"; import { sha256 } from "./digest.ts"; +import { keyFromAuthData } from "./common-crypto/key-passphrase.ts"; export type Store = IStore; From 08c19dbcc1440eca42c4c2e6079ae67d89114f1d Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 13 Sep 2024 18:03:27 +0200 Subject: [PATCH 07/12] Keep `keyFromPassphrase` in old crypto --- src/crypto/backup.ts | 3 ++- src/crypto/index.ts | 2 +- src/crypto/key_passphrase.ts | 26 +++++++++++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/crypto/backup.ts b/src/crypto/backup.ts index a7381f5a095..2dc374f9868 100644 --- a/src/crypto/backup.ts +++ b/src/crypto/backup.ts @@ -24,6 +24,7 @@ import { logger } from "../logger.ts"; import { MEGOLM_ALGORITHM, verifySignature } from "./olmlib.ts"; import { DeviceInfo } from "./deviceinfo.ts"; import { DeviceTrustLevel } from "./CrossSigning.ts"; +import { keyFromPassphrase } from "./key_passphrase.ts"; import { encodeUri, safeSet, sleep } from "../utils.ts"; import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store.ts"; import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes.ts"; @@ -39,7 +40,7 @@ import { CryptoEvent } from "./index.ts"; import { ClientPrefix, HTTPError, MatrixError, Method } from "../http-api/index.ts"; import { BackupTrustInfo } from "../crypto-api/keybackup.ts"; import { BackupDecryptor } from "../common-crypto/CryptoBackend.ts"; -import { encodeRecoveryKey, keyFromPassphrase } from "../crypto-api/index.ts"; +import { encodeRecoveryKey } from "../crypto-api/index.ts"; const KEY_BACKUP_KEYS_PER_REQUEST = 200; const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms diff --git a/src/crypto/index.ts b/src/crypto/index.ts index b1724d21492..d72e41a6e3d 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -41,6 +41,7 @@ import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store.ts"; import { VerificationBase } from "./verification/Base.ts"; import { ReciprocateQRCode, SCAN_QR_CODE_METHOD, SHOW_QR_CODE_METHOD } from "./verification/QRCode.ts"; import { SAS as SASVerification } from "./verification/SAS.ts"; +import { keyFromPassphrase } from "./key_passphrase.ts"; import { VerificationRequest } from "./verification/request/VerificationRequest.ts"; import { InRoomChannel, InRoomRequests } from "./verification/request/InRoomChannel.ts"; import { Request, ToDeviceChannel, ToDeviceRequests } from "./verification/request/ToDeviceChannel.ts"; @@ -97,7 +98,6 @@ import { ImportRoomKeysOpts, KeyBackupCheck, KeyBackupInfo, - keyFromPassphrase, OwnDeviceKeys, VerificationRequest as CryptoApiVerificationRequest, } from "../crypto-api/index.ts"; diff --git a/src/crypto/key_passphrase.ts b/src/crypto/key_passphrase.ts index 683066abdcb..4f9035473ba 100644 --- a/src/crypto/key_passphrase.ts +++ b/src/crypto/key_passphrase.ts @@ -14,5 +14,29 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { randomString } from "../randomstring.ts"; +import { deriveRecoveryKeyFromPassphrase } from "../crypto-api/index.ts"; + +const DEFAULT_ITERATIONS = 500000; + +interface IKey { + key: Uint8Array; + salt: string; + iterations: number; +} + +/** + * Generate a new recovery key, based on a passphrase. + * @param passphrase - The passphrase to generate the key from + */ +export async function keyFromPassphrase(passphrase: string): Promise { + const salt = randomString(32); + + const key = await deriveRecoveryKeyFromPassphrase(passphrase, salt, DEFAULT_ITERATIONS); + + return { key, salt, iterations: DEFAULT_ITERATIONS }; +} + // Re-export the key passphrase functions to avoid breaking changes -export * from "../crypto-api/key-passphrase.ts"; +export { deriveRecoveryKeyFromPassphrase as deriveKey }; +export { keyFromAuthData } from "../common-crypto/key-passphrase.ts"; From e360de4cb136c20f7920d02215154d29289ba521 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 13 Sep 2024 18:10:21 +0200 Subject: [PATCH 08/12] - Rename `deriveKey` into `deriveRecoveryKeyFromPassphrase` - Call `deriveRecoveryKeyFromPassphrase` into `RustCrypto.createRecoveryKeyFromPassphrase` instead of using `keyFromPassphrase` --- src/common-crypto/key-passphrase.ts | 8 ++--- src/crypto-api/key-passphrase.ts | 46 ++++++++--------------------- src/rust-crypto/rust-crypto.ts | 25 ++++++++++++---- 3 files changed, 34 insertions(+), 45 deletions(-) diff --git a/src/common-crypto/key-passphrase.ts b/src/common-crypto/key-passphrase.ts index f81286a5af3..31b699a65c9 100644 --- a/src/common-crypto/key-passphrase.ts +++ b/src/common-crypto/key-passphrase.ts @@ -14,9 +14,7 @@ * limitations under the License. */ -import { deriveKey } from "../crypto-api/index.ts"; - -const DEFAULT_BIT_SIZE = 256; +import { deriveRecoveryKeyFromPassphrase } from "../crypto-api/index.ts"; /* eslint-disable camelcase */ interface IAuthData { @@ -35,10 +33,10 @@ export function keyFromAuthData(authData: IAuthData, passphrase: string): Promis throw new Error("Salt and/or iterations not found: " + "this backup cannot be restored with a passphrase"); } - return deriveKey( + return deriveRecoveryKeyFromPassphrase( passphrase, authData.private_key_salt, authData.private_key_iterations, - authData.private_key_bits || DEFAULT_BIT_SIZE, + authData.private_key_bits, ); } diff --git a/src/crypto-api/key-passphrase.ts b/src/crypto-api/key-passphrase.ts index 9c31a6189d5..1bd8745017a 100644 --- a/src/crypto-api/key-passphrase.ts +++ b/src/crypto-api/key-passphrase.ts @@ -14,44 +14,22 @@ * limitations under the License. */ -import { randomString } from "../randomstring.ts"; - -const DEFAULT_ITERATIONS = 500000; - -const DEFAULT_BITSIZE = 256; - -/* eslint-enable camelcase */ - -interface IKey { - key: Uint8Array; - salt: string; - iterations: number; -} - -/** - * Derive a key from a passphrase. - * @param password - */ -export async function keyFromPassphrase(password: string): Promise { - const salt = randomString(32); - - const key = await deriveKey(password, salt, DEFAULT_ITERATIONS, DEFAULT_BITSIZE); - - return { key, salt, iterations: DEFAULT_ITERATIONS }; -} +const DEFAULT_BIT_SIZE = 256; /** - * Derive a key from a passphrase using PBKDF2. - * @param password - * @param salt - * @param iterations - * @param numBits + * Derive a recovery key from a passphrase and salt using PBKDF2. + * @see https://spec.matrix.org/v1.11/client-server-api/#deriving-keys-from-passphrases + * + * @param passphrase - The passphrase to derive the key from + * @param salt - The salt to use in the derivation + * @param iterations - The number of iterations to use in the derivation + * @param numBits - The number of bits to derive */ -export async function deriveKey( - password: string, +export async function deriveRecoveryKeyFromPassphrase( + passphrase: string, salt: string, iterations: number, - numBits = DEFAULT_BITSIZE, + numBits = DEFAULT_BIT_SIZE, ): Promise { if (!globalThis.crypto.subtle || !TextEncoder) { throw new Error("Password-based backup is not available on this platform"); @@ -59,7 +37,7 @@ export async function deriveKey( const key = await globalThis.crypto.subtle.importKey( "raw", - new TextEncoder().encode(password), + new TextEncoder().encode(passphrase), { name: "PBKDF2" }, false, ["deriveBits"], diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index 28f85eca9e3..311188debb9 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -55,11 +55,11 @@ import { ImportRoomKeysOpts, KeyBackupCheck, KeyBackupInfo, - keyFromPassphrase, OwnDeviceKeys, UserVerificationStatus, VerificationRequest, encodeRecoveryKey, + deriveRecoveryKeyFromPassphrase, } from "../crypto-api/index.ts"; import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter.ts"; import { IDownloadKeyResult, IQueryKeysRequest } from "../client.ts"; @@ -100,6 +100,12 @@ interface ISignableObject { * @internal */ export class RustCrypto extends TypedEventEmitter implements CryptoBackend { + /** + * The number of iterations to use when deriving a recovery key from a passphrase. + * @private + */ + private readonly RECOVERY_KEY_DERIVATION_ITERATION = 500000; + private _trustCrossSignedDevices = true; /** whether {@link stop} has been called */ @@ -879,17 +885,24 @@ export class RustCrypto extends TypedEventEmitter { if (password) { // Generate the key from the passphrase - const derivation = await keyFromPassphrase(password); + // first we generate a random salt + const salt = randomString(32); + // then we derive the key from the passphrase + const recoveryKey = await deriveRecoveryKeyFromPassphrase( + password, + salt, + this.RECOVERY_KEY_DERIVATION_ITERATION, + ); return { keyInfo: { passphrase: { algorithm: "m.pbkdf2", - iterations: derivation.iterations, - salt: derivation.salt, + iterations: this.RECOVERY_KEY_DERIVATION_ITERATION, + salt, }, }, - privateKey: derivation.key, - encodedPrivateKey: encodeRecoveryKey(derivation.key), + privateKey: recoveryKey, + encodedPrivateKey: encodeRecoveryKey(recoveryKey), }; } else { // Using the navigator crypto API to generate the private key From 00e9e8e5d3d0b60b557d69d06d6ae1d12339d98d Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 13 Sep 2024 18:12:52 +0200 Subject: [PATCH 09/12] Remove alternative in `keyBackupKeyFromPassword` deprecation. --- src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index c7056c42695..428eb0b84ba 100644 --- a/src/client.ts +++ b/src/client.ts @@ -3656,7 +3656,7 @@ export class MatrixClient extends TypedEventEmitter { return keyFromAuthData(backupInfo.auth_data, password); From 8845de92887f47146f430f09304382304f35f52d Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 16 Sep 2024 10:31:34 +0200 Subject: [PATCH 10/12] Add tests for `keyFromAuthData` --- .../unit/common-crypto/key-passphrase.spec.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 spec/unit/common-crypto/key-passphrase.spec.ts diff --git a/spec/unit/common-crypto/key-passphrase.spec.ts b/spec/unit/common-crypto/key-passphrase.spec.ts new file mode 100644 index 00000000000..933b5f1834c --- /dev/null +++ b/spec/unit/common-crypto/key-passphrase.spec.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2024 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { keyFromAuthData } from "../../../src/common-crypto/key-passphrase.ts"; + +describe("key-passphrase", () => { + describe("keyFromAuthData", () => { + it("should throw an error if salt or iterations are missing", async () => { + // missing salt + expect(() => keyFromAuthData({ private_key_iterations: 5 }, "passphrase")).toThrow( + "Salt and/or iterations not found: this backup cannot be restored with a passphrase", + ); + + // missing iterations + expect(() => keyFromAuthData({ private_key_salt: "salt" }, "passphrase")).toThrow( + "Salt and/or iterations not found: this backup cannot be restored with a passphrase", + ); + }); + + it("should derive key from auth data", async () => { + const key = await keyFromAuthData({ private_key_salt: "salt", private_key_iterations: 5 }, "passphrase"); + expect(key).toBeDefined(); + }); + }); +}); From 32a2719b7a16c604d5642f7b66922e50f4fc899d Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 16 Sep 2024 12:07:13 +0200 Subject: [PATCH 11/12] Deprecate `keyFromAuthData` --- src/common-crypto/key-passphrase.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common-crypto/key-passphrase.ts b/src/common-crypto/key-passphrase.ts index 31b699a65c9..39debea54f2 100644 --- a/src/common-crypto/key-passphrase.ts +++ b/src/common-crypto/key-passphrase.ts @@ -27,6 +27,7 @@ interface IAuthData { * Derive a backup key from a passphrase using the salt and iterations from the auth data. * @param authData - The auth data containing the salt and iterations * @param passphrase - The passphrase to derive the key from + * @deprecated */ export function keyFromAuthData(authData: IAuthData, passphrase: string): Promise { if (!authData.private_key_salt || !authData.private_key_iterations) { From 705433e0885a97bae830a2086a27e835f4dc8c3d Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 17 Sep 2024 15:01:24 +0200 Subject: [PATCH 12/12] Review changes --- src/client.ts | 2 +- src/common-crypto/key-passphrase.ts | 2 +- src/rust-crypto/rust-crypto.ts | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/client.ts b/src/client.ts index 428eb0b84ba..9273281a076 100644 --- a/src/client.ts +++ b/src/client.ts @@ -3656,7 +3656,7 @@ export class MatrixClient extends TypedEventEmitter { return keyFromAuthData(backupInfo.auth_data, password); diff --git a/src/common-crypto/key-passphrase.ts b/src/common-crypto/key-passphrase.ts index 39debea54f2..27b1df64583 100644 --- a/src/common-crypto/key-passphrase.ts +++ b/src/common-crypto/key-passphrase.ts @@ -27,7 +27,7 @@ interface IAuthData { * Derive a backup key from a passphrase using the salt and iterations from the auth data. * @param authData - The auth data containing the salt and iterations * @param passphrase - The passphrase to derive the key from - * @deprecated + * @deprecated Deriving a backup key from a passphrase is not part of the matrix spec. Instead, a random key is generated and stored/shared via 4S. */ export function keyFromAuthData(authData: IAuthData, passphrase: string): Promise { if (!authData.private_key_salt || !authData.private_key_iterations) { diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index 311188debb9..0a661338111 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -102,9 +102,8 @@ interface ISignableObject { export class RustCrypto extends TypedEventEmitter implements CryptoBackend { /** * The number of iterations to use when deriving a recovery key from a passphrase. - * @private */ - private readonly RECOVERY_KEY_DERIVATION_ITERATION = 500000; + private readonly RECOVERY_KEY_DERIVATION_ITERATIONS = 500000; private _trustCrossSignedDevices = true; @@ -891,13 +890,13 @@ export class RustCrypto extends TypedEventEmitter