1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-31 15:24:23 +03:00

ElementR: Add rust-crypto#createRecoveryKeyFromPassphrase implementation (#3472)

* Add `rust-crypto#createRecoveryKeyFromPassphrase` implementation

* Use `crypto`

* Rename `IRecoveryKey` into `GeneratedSecretStorageKey` for rust crypto

* Improve comments

* Improve `createRecoveryKeyFromPassphrase`
This commit is contained in:
Florian Duros
2023-06-14 16:38:43 +02:00
committed by GitHub
parent d14fc426e6
commit 0545f6df09
4 changed files with 98 additions and 11 deletions

View File

@ -356,6 +356,39 @@ describe("RustCrypto", () => {
expect(res).toBe(null); expect(res).toBe(null);
}); });
}); });
describe("createRecoveryKeyFromPassphrase", () => {
let rustCrypto: RustCrypto;
beforeEach(async () => {
rustCrypto = await makeTestRustCrypto();
});
it("should create a recovery key without password", async () => {
const recoveryKey = await rustCrypto.createRecoveryKeyFromPassphrase();
// Expected the encoded private key to have 59 chars
expect(recoveryKey.encodedPrivateKey?.length).toBe(59);
// Expect the private key to be an Uint8Array with a length of 32
expect(recoveryKey.privateKey).toBeInstanceOf(Uint8Array);
expect(recoveryKey.privateKey.length).toBe(32);
// Expect keyInfo to be empty
expect(Object.keys(recoveryKey.keyInfo!).length).toBe(0);
});
it("should create a recovery key with password", async () => {
const recoveryKey = await rustCrypto.createRecoveryKeyFromPassphrase("my password");
// Expected the encoded private key to have 59 chars
expect(recoveryKey.encodedPrivateKey?.length).toBe(59);
// Expect the private key to be an Uint8Array with a length of 32
expect(recoveryKey.privateKey).toBeInstanceOf(Uint8Array);
expect(recoveryKey.privateKey.length).toBe(32);
// Expect keyInfo.passphrase to be filled
expect(recoveryKey.keyInfo?.passphrase?.algorithm).toBe("m.pbkdf2");
expect(recoveryKey.keyInfo?.passphrase?.iterations).toBe(500000);
});
});
}); });
/** build a basic RustCrypto instance for testing /** build a basic RustCrypto instance for testing

View File

@ -18,6 +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 } from "./secret-storage";
/** Types of cross-signing key */ /** Types of cross-signing key */
export enum CrossSigningKey { export enum CrossSigningKey {
@ -26,6 +27,17 @@ export enum CrossSigningKey {
UserSigning = "user_signing", UserSigning = "user_signing",
} }
/**
* Recovery key created by {@link CryptoApi#createRecoveryKeyFromPassphrase}
*/
export interface GeneratedSecretStorageKey {
keyInfo?: AddSecretStorageKeyOpts;
/** The raw generated private key. */
privateKey: Uint8Array;
/** The generated key, encoded for display to the user per https://spec.matrix.org/v1.7/client-server-api/#key-representation. */
encodedPrivateKey?: string;
}
/** /**
* Public interface to the cryptography parts of the js-sdk * Public interface to the cryptography parts of the js-sdk
* *
@ -201,6 +213,20 @@ export interface CryptoApi {
* @returns The current status of cross-signing keys: whether we have public and private keys cached locally, and whether the private keys are in secret storage. * @returns The current status of cross-signing keys: whether we have public and private keys cached locally, and whether the private keys are in secret storage.
*/ */
getCrossSigningStatus(): Promise<CrossSigningStatus>; getCrossSigningStatus(): Promise<CrossSigningStatus>;
/**
* Create a recovery key (ie, a key suitable for use with server-side secret storage).
*
* The key can either be based on a user-supplied passphrase, or just created randomly.
*
* @param password - Optional passphrase string to use to derive the key,
* which can later be entered by the user as an alternative to entering the
* recovery key itself. If omitted, a key is generated randomly.
*
* @returns Object including recovery key and server upload parameters.
* The private key should be disposed of after displaying to the use.
*/
createRecoveryKeyFromPassphrase(password?: string): Promise<GeneratedSecretStorageKey>;
} }
/** /**

View File

@ -16,10 +16,10 @@ limitations under the License.
import { DeviceInfo } from "./deviceinfo"; import { DeviceInfo } from "./deviceinfo";
import { IKeyBackupInfo } from "./keybackup"; import { IKeyBackupInfo } from "./keybackup";
import type { AddSecretStorageKeyOpts } from "../secret-storage"; import { GeneratedSecretStorageKey } from "../crypto-api";
/* re-exports for backwards compatibility. */ /* re-exports for backwards compatibility. */
export { CrossSigningKey } from "../crypto-api"; export { CrossSigningKey, GeneratedSecretStorageKey as IRecoveryKey } from "../crypto-api";
export type { export type {
ImportRoomKeyProgressData as IImportOpts, ImportRoomKeyProgressData as IImportOpts,
@ -66,12 +66,6 @@ export interface IEncryptedEventInfo {
mismatchedSender: boolean; mismatchedSender: boolean;
} }
export interface IRecoveryKey {
keyInfo?: AddSecretStorageKeyOpts;
privateKey: Uint8Array;
encodedPrivateKey?: string;
}
export interface ICreateSecretStorageOpts { export interface ICreateSecretStorageOpts {
/** /**
* Function called to await a secret storage key creation flow. * Function called to await a secret storage key creation flow.
@ -79,7 +73,7 @@ export interface ICreateSecretStorageOpts {
* recovery key which should be disposed of after displaying to the user, * recovery key which should be disposed of after displaying to the user,
* and raw private key to avoid round tripping if needed. * and raw private key to avoid round tripping if needed.
*/ */
createSecretStorageKey?: () => Promise<IRecoveryKey>; createSecretStorageKey?: () => Promise<GeneratedSecretStorageKey>;
/** /**
* The current key backup object. If passed, * The current key backup object. If passed,

View File

@ -34,16 +34,20 @@ import {
BootstrapCrossSigningOpts, BootstrapCrossSigningOpts,
CrossSigningStatus, CrossSigningStatus,
DeviceVerificationStatus, DeviceVerificationStatus,
GeneratedSecretStorageKey,
ImportRoomKeyProgressData, ImportRoomKeyProgressData,
ImportRoomKeysOpts, ImportRoomKeysOpts,
CrossSigningKey,
} from "../crypto-api"; } from "../crypto-api";
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 { ServerSideSecretStorage } from "../secret-storage"; import { AddSecretStorageKeyOpts, ServerSideSecretStorage } from "../secret-storage";
import { CrossSigningKey } from "../crypto/api";
import { CrossSigningIdentity } from "./CrossSigningIdentity"; import { CrossSigningIdentity } from "./CrossSigningIdentity";
import { secretStorageContainsCrossSigningKeys } from "./secret-storage"; import { secretStorageContainsCrossSigningKeys } from "./secret-storage";
import { keyFromPassphrase } from "../crypto/key_passphrase";
import { encodeRecoveryKey } from "../crypto/recoverykey";
import { crypto } from "../crypto/crypto";
/** /**
* An implementation of {@link CryptoBackend} using the Rust matrix-sdk-crypto. * An implementation of {@link CryptoBackend} using the Rust matrix-sdk-crypto.
@ -405,6 +409,36 @@ export class RustCrypto implements CryptoBackend {
}; };
} }
/**
* Implementation of {@link CryptoApi#createRecoveryKeyFromPassphrase}
*/
public async createRecoveryKeyFromPassphrase(password?: string): Promise<GeneratedSecretStorageKey> {
let key: Uint8Array;
const keyInfo: AddSecretStorageKeyOpts = {};
if (password) {
// Generate the key from the passphrase
const derivation = await keyFromPassphrase(password);
keyInfo.passphrase = {
algorithm: "m.pbkdf2",
iterations: derivation.iterations,
salt: derivation.salt,
};
key = derivation.key;
} else {
// Using the navigator crypto API to generate the private key
key = new Uint8Array(32);
crypto.getRandomValues(key);
}
const encodedPrivateKey = encodeRecoveryKey(key);
return {
keyInfo,
encodedPrivateKey,
privateKey: key,
};
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// SyncCryptoCallbacks implementation // SyncCryptoCallbacks implementation