Switch to Rust crypto stack for all logins (#12630)
* Use Rust crypto stack universally Ignore the `feature_rust_crypto` and `RustCrypto.staged_rollout_percent` settings, and just use RustCrypto everywhere. * Remove labs setting for rust crypto * Remove support for legacy crypto stack in `StorageManager` We're not going to use the legacy stack any more. * Update docs on `Features.RustCrypto` * Remove now-unreachable `tryToUnlockSecretStorageWithDehydrationKey` * Comment out test which doesn't work * fix typo
This commit is contained in:
+15
-64
@@ -40,10 +40,9 @@ import Modal from "./Modal";
|
||||
import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientBackedSettingsHandler";
|
||||
import * as StorageManager from "./utils/StorageManager";
|
||||
import IdentityAuthClient from "./IdentityAuthClient";
|
||||
import { crossSigningCallbacks, tryToUnlockSecretStorageWithDehydrationKey } from "./SecurityManager";
|
||||
import { crossSigningCallbacks } from "./SecurityManager";
|
||||
import { ModuleRunner } from "./modules/ModuleRunner";
|
||||
import { SlidingSyncManager } from "./SlidingSyncManager";
|
||||
import CryptoStoreTooNewDialog from "./components/views/dialogs/CryptoStoreTooNewDialog";
|
||||
import { _t, UserFriendlyError } from "./languageHandler";
|
||||
import { SettingLevel } from "./settings/SettingLevel";
|
||||
import MatrixClientBackedController from "./settings/controllers/MatrixClientBackedController";
|
||||
@@ -52,7 +51,6 @@ import PlatformPeg from "./PlatformPeg";
|
||||
import { formatList } from "./utils/FormattingUtils";
|
||||
import SdkConfig from "./SdkConfig";
|
||||
import { Features } from "./settings/Settings";
|
||||
import { PhasedRolloutFeature } from "./utils/PhasedRolloutFeature";
|
||||
|
||||
export interface IMatrixClientCreds {
|
||||
homeserverUrl: string;
|
||||
@@ -326,7 +324,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
|
||||
/**
|
||||
* Attempt to initialize the crypto layer on a newly-created MatrixClient
|
||||
*
|
||||
* @param rustCryptoStoreKey - If we are using Rust crypto, a key with which to encrypt the indexeddb.
|
||||
* @param rustCryptoStoreKey - A key with which to encrypt the rust crypto indexeddb.
|
||||
* If provided, it must be exactly 32 bytes of data. If both this and `rustCryptoStorePassword` are
|
||||
* undefined, the store will be unencrypted.
|
||||
*
|
||||
@@ -339,70 +337,23 @@ class MatrixClientPegClass implements IMatrixClientPeg {
|
||||
throw new Error("createClient must be called first");
|
||||
}
|
||||
|
||||
let useRustCrypto = SettingsStore.getValue(Features.RustCrypto);
|
||||
|
||||
// We want the value that is set in the config.json for that web instance
|
||||
const defaultUseRustCrypto = SettingsStore.getValueAt(SettingLevel.CONFIG, Features.RustCrypto);
|
||||
const migrationPercent = SettingsStore.getValueAt(SettingLevel.CONFIG, "RustCrypto.staged_rollout_percent");
|
||||
|
||||
// If the default config is to use rust crypto, and the user is on legacy crypto,
|
||||
// we want to check if we should migrate the current user.
|
||||
if (!useRustCrypto && defaultUseRustCrypto && Number.isInteger(migrationPercent)) {
|
||||
// The user is not on rust crypto, but the default stack is now rust; Let's check if we should migrate
|
||||
// the current user to rust crypto.
|
||||
try {
|
||||
const stagedRollout = new PhasedRolloutFeature("RustCrypto.staged_rollout_percent", migrationPercent);
|
||||
// Device id should not be null at that point, or init crypto will fail anyhow
|
||||
const deviceId = this.matrixClient.getDeviceId()!;
|
||||
// we use deviceId rather than userId because we don't particularly want all devices
|
||||
// of a user to be migrated at the same time.
|
||||
useRustCrypto = stagedRollout.isFeatureEnabled(deviceId);
|
||||
} catch (e) {
|
||||
logger.warn("Failed to create staged rollout feature for rust crypto migration", e);
|
||||
}
|
||||
if (!rustCryptoStoreKey && !rustCryptoStorePassword) {
|
||||
logger.error("Warning! Not using an encryption key for rust crypto store.");
|
||||
}
|
||||
|
||||
// we want to make sure that the same crypto implementation is used throughout the lifetime of a device,
|
||||
// so persist the setting at the device layer
|
||||
// (At some point, we'll allow the user to *enable* the setting via labs, which will migrate their existing
|
||||
// device to the rust-sdk implementation, but that won't change anything here).
|
||||
await SettingsStore.setValue(Features.RustCrypto, null, SettingLevel.DEVICE, useRustCrypto);
|
||||
// Record the fact that we used the Rust crypto stack with this client. This just guards against people
|
||||
// rolling back to versions of EW that did not default to Rust crypto (which would lead to an error, since
|
||||
// we cannot migrate from Rust to Legacy crypto).
|
||||
await SettingsStore.setValue(Features.RustCrypto, null, SettingLevel.DEVICE, true);
|
||||
|
||||
// Now we can initialise the right crypto impl.
|
||||
if (useRustCrypto) {
|
||||
if (!rustCryptoStoreKey && !rustCryptoStorePassword) {
|
||||
logger.error("Warning! Not using an encryption key for rust crypto store.");
|
||||
}
|
||||
await this.matrixClient.initRustCrypto({
|
||||
storageKey: rustCryptoStoreKey,
|
||||
storagePassword: rustCryptoStorePassword,
|
||||
});
|
||||
await this.matrixClient.initRustCrypto({
|
||||
storageKey: rustCryptoStoreKey,
|
||||
storagePassword: rustCryptoStorePassword,
|
||||
});
|
||||
|
||||
StorageManager.setCryptoInitialised(true);
|
||||
// TODO: device dehydration and whathaveyou
|
||||
return;
|
||||
}
|
||||
|
||||
// fall back to the libolm layer.
|
||||
try {
|
||||
// check that we have a version of the js-sdk which includes initCrypto
|
||||
if (this.matrixClient.initCrypto) {
|
||||
await this.matrixClient.initCrypto();
|
||||
this.matrixClient.setCryptoTrustCrossSignedDevices(
|
||||
!SettingsStore.getValue("e2ee.manuallyVerifyAllSessions"),
|
||||
);
|
||||
await tryToUnlockSecretStorageWithDehydrationKey(this.matrixClient);
|
||||
StorageManager.setCryptoInitialised(true);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.name === "InvalidCryptoStoreError") {
|
||||
// The js-sdk found a crypto DB too new for it to use
|
||||
Modal.createDialog(CryptoStoreTooNewDialog);
|
||||
}
|
||||
// this can happen for a number of reasons, the most likely being
|
||||
// that the olm library was missing. It's not fatal.
|
||||
logger.warn("Unable to initialise e2e", e);
|
||||
}
|
||||
StorageManager.setCryptoInitialised(true);
|
||||
// TODO: device dehydration and whathaveyou
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-56
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Crypto, ICryptoCallbacks, MatrixClient, encodeBase64, SecretStorage } from "matrix-js-sdk/src/matrix";
|
||||
import { Crypto, ICryptoCallbacks, encodeBase64, SecretStorage } from "matrix-js-sdk/src/matrix";
|
||||
import { deriveKey } from "matrix-js-sdk/src/crypto/key_passphrase";
|
||||
import { decodeRecoveryKey } from "matrix-js-sdk/src/crypto/recoverykey";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
@@ -40,8 +40,6 @@ let secretStorageKeys: Record<string, Uint8Array> = {};
|
||||
let secretStorageKeyInfo: Record<string, SecretStorage.SecretStorageKeyDescription> = {};
|
||||
let secretStorageBeingAccessed = false;
|
||||
|
||||
let nonInteractive = false;
|
||||
|
||||
let dehydrationCache: {
|
||||
key?: Uint8Array;
|
||||
keyInfo?: SecretStorage.SecretStorageKeyDescription;
|
||||
@@ -138,10 +136,6 @@ async function getSecretStorageKey({
|
||||
return [keyId, keyFromCustomisations];
|
||||
}
|
||||
|
||||
if (nonInteractive) {
|
||||
throw new Error("Could not unlock non-interactively");
|
||||
}
|
||||
|
||||
const inputToKey = makeInputToKey(keyInfo);
|
||||
const { finished } = Modal.createDialog(
|
||||
AccessSecretStorageDialog,
|
||||
@@ -430,52 +424,3 @@ async function doAccessSecretStorage(func: () => Promise<void>, forceReset: bool
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: this function name is a bit of a mouthful
|
||||
export async function tryToUnlockSecretStorageWithDehydrationKey(client: MatrixClient): Promise<void> {
|
||||
const key = dehydrationCache.key;
|
||||
let restoringBackup = false;
|
||||
if (key && (await client.isSecretStorageReady())) {
|
||||
logger.log("Trying to set up cross-signing using dehydration key");
|
||||
secretStorageBeingAccessed = true;
|
||||
nonInteractive = true;
|
||||
try {
|
||||
await client.checkOwnCrossSigningTrust();
|
||||
|
||||
// we also need to set a new dehydrated device to replace the
|
||||
// device we rehydrated
|
||||
let dehydrationKeyInfo = {};
|
||||
if (dehydrationCache.keyInfo && dehydrationCache.keyInfo.passphrase) {
|
||||
dehydrationKeyInfo = { passphrase: dehydrationCache.keyInfo.passphrase };
|
||||
}
|
||||
await client.setDehydrationKey(key, dehydrationKeyInfo, "Backup device");
|
||||
|
||||
// and restore from backup
|
||||
const backupInfo = await client.getKeyBackupVersion();
|
||||
if (backupInfo) {
|
||||
restoringBackup = true;
|
||||
// don't await, because this can take a long time
|
||||
client.restoreKeyBackupWithSecretStorage(backupInfo).finally(() => {
|
||||
secretStorageBeingAccessed = false;
|
||||
nonInteractive = false;
|
||||
if (!isCachingAllowed()) {
|
||||
secretStorageKeys = {};
|
||||
secretStorageKeyInfo = {};
|
||||
}
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
dehydrationCache = {};
|
||||
// the secret storage cache is needed for restoring from backup, so
|
||||
// don't clear it yet if we're restoring from backup
|
||||
if (!restoringBackup) {
|
||||
secretStorageBeingAccessed = false;
|
||||
nonInteractive = false;
|
||||
if (!isCachingAllowed()) {
|
||||
secretStorageKeys = {};
|
||||
secretStorageKeyInfo = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1465,11 +1465,6 @@
|
||||
"render_reaction_images_description": "Sometimes referred to as \"custom emojis\".",
|
||||
"report_to_moderators": "Report to moderators",
|
||||
"report_to_moderators_description": "In rooms that support moderation, the “Report” button will let you report abuse to room moderators.",
|
||||
"rust_crypto": "Rust cryptography implementation",
|
||||
"rust_crypto_in_config": "Rust cryptography cannot be disabled on this deployment of %(brand)s",
|
||||
"rust_crypto_in_config_description": "Switching to the Rust cryptography requires a migration process that may take several minutes. It cannot be disabled; use with caution!",
|
||||
"rust_crypto_optin_warning": "Switching to the Rust cryptography requires a migration process that may take several minutes. To disable you will need to log out and back in; use with caution!",
|
||||
"rust_crypto_requires_logout": "Once enabled, Rust cryptography can only be disabled by logging out and in again",
|
||||
"sliding_sync": "Sliding Sync mode",
|
||||
"sliding_sync_description": "Under active development, cannot be disabled.",
|
||||
"sliding_sync_disabled_notice": "Log out and back in to disable",
|
||||
|
||||
@@ -42,11 +42,9 @@ import { MetaSpace } from "../stores/spaces";
|
||||
import SdkConfig from "../SdkConfig";
|
||||
import SlidingSyncController from "./controllers/SlidingSyncController";
|
||||
import { FontWatcher } from "./watchers/FontWatcher";
|
||||
import RustCryptoSdkController from "./controllers/RustCryptoSdkController";
|
||||
import ServerSupportUnstableFeatureController from "./controllers/ServerSupportUnstableFeatureController";
|
||||
import { WatchManager } from "./WatchManager";
|
||||
import { CustomTheme } from "../theme";
|
||||
import SettingsStore from "./SettingsStore";
|
||||
import AnalyticsController from "./controllers/AnalyticsController";
|
||||
|
||||
export const defaultWatchManager = new WatchManager();
|
||||
@@ -99,9 +97,14 @@ export enum Features {
|
||||
VoiceBroadcastForceSmallChunks = "feature_voice_broadcast_force_small_chunks",
|
||||
NotificationSettings2 = "feature_notification_settings2",
|
||||
OidcNativeFlow = "feature_oidc_native_flow",
|
||||
// If true, every new login will use the new rust crypto implementation
|
||||
RustCrypto = "feature_rust_crypto",
|
||||
ReleaseAnnouncement = "feature_release_announcement",
|
||||
|
||||
/** If true, use the Rust crypto implementation.
|
||||
*
|
||||
* This is no longer read, but we continue to populate it on all devices, to guard against people rolling back to
|
||||
* old versions of EW that do not use rust crypto by default.
|
||||
*/
|
||||
RustCrypto = "feature_rust_crypto",
|
||||
}
|
||||
|
||||
export const labGroupNames: Record<LabGroup, TranslationKey> = {
|
||||
@@ -480,29 +483,8 @@ export const SETTINGS: { [setting: string]: ISetting } = {
|
||||
default: false,
|
||||
},
|
||||
[Features.RustCrypto]: {
|
||||
// use the rust matrix-sdk-crypto-wasm for crypto.
|
||||
isFeature: true,
|
||||
labsGroup: LabGroup.Developer,
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
|
||||
displayName: _td("labs|rust_crypto"),
|
||||
description: () => {
|
||||
if (SettingsStore.getValueAt(SettingLevel.CONFIG, Features.RustCrypto)) {
|
||||
// It's enabled in the config, so you can't get rid of it even by logging out.
|
||||
return _t("labs|rust_crypto_in_config_description");
|
||||
} else {
|
||||
return _t("labs|rust_crypto_optin_warning");
|
||||
}
|
||||
},
|
||||
shouldWarn: true,
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||
default: true,
|
||||
controller: new RustCryptoSdkController(),
|
||||
},
|
||||
// Must be set under `setting_defaults` in config.json.
|
||||
// If set to 100 in conjunction with `feature_rust_crypto`, all existing users will migrate to the new crypto.
|
||||
// Default is 0, meaning no existing users on legacy crypto will migrate.
|
||||
"RustCrypto.staged_rollout_percent": {
|
||||
supportedLevels: [SettingLevel.CONFIG],
|
||||
default: 0,
|
||||
},
|
||||
/**
|
||||
* @deprecated in favor of {@link fontSizeDelta}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 { _t } from "../../languageHandler";
|
||||
import SettingsStore from "../SettingsStore";
|
||||
import { SettingLevel } from "../SettingLevel";
|
||||
import PlatformPeg from "../../PlatformPeg";
|
||||
import SettingController from "./SettingController";
|
||||
import { Features } from "../Settings";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
|
||||
export default class RustCryptoSdkController extends SettingController {
|
||||
public onChange(level: SettingLevel, roomId: string | null, newValue: any): void {
|
||||
// If the crypto stack has already been initialized, we'll need to reload the app to make it take effect.
|
||||
if (MatrixClientPeg.get()?.getCrypto()) {
|
||||
PlatformPeg.get()?.reload();
|
||||
}
|
||||
}
|
||||
|
||||
public get settingDisabled(): boolean | string {
|
||||
if (!SettingsStore.getValueAt(SettingLevel.DEVICE, Features.RustCrypto)) {
|
||||
// If rust crypto has not yet been enabled for this device, you can turn it on, IF YOU DARE
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SettingsStore.getValueAt(SettingLevel.CONFIG, Features.RustCrypto)) {
|
||||
// It's enabled in the config, so you can't get rid of it even by logging out.
|
||||
return _t("labs|rust_crypto_in_config", { brand: SdkConfig.get().brand });
|
||||
}
|
||||
|
||||
// The setting is enabled at the device level, but not mandated at the config level.
|
||||
// You can only turn it off by logging out and in again.
|
||||
return _t("labs|rust_crypto_requires_logout");
|
||||
}
|
||||
}
|
||||
+25
-48
@@ -14,11 +14,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { LocalStorageCryptoStore, IndexedDBStore, IndexedDBCryptoStore } from "matrix-js-sdk/src/matrix";
|
||||
import { IndexedDBStore, IndexedDBCryptoStore } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import { Features } from "../settings/Settings";
|
||||
import { getIDBFactory } from "./StorageAccess";
|
||||
|
||||
const localStorage = window.localStorage;
|
||||
@@ -141,55 +139,34 @@ async function checkSyncStore(): Promise<StoreCheck> {
|
||||
}
|
||||
|
||||
async function checkCryptoStore(): Promise<StoreCheck> {
|
||||
if (await SettingsStore.getValue(Features.RustCrypto)) {
|
||||
// check first if there is a rust crypto store
|
||||
try {
|
||||
const rustDbExists = await IndexedDBCryptoStore.exists(getIDBFactory()!, RUST_CRYPTO_STORE_NAME);
|
||||
log(`Rust Crypto store using IndexedDB contains data? ${rustDbExists}`);
|
||||
// check first if there is a rust crypto store
|
||||
try {
|
||||
const rustDbExists = await IndexedDBCryptoStore.exists(getIDBFactory()!, RUST_CRYPTO_STORE_NAME);
|
||||
log(`Rust Crypto store using IndexedDB contains data? ${rustDbExists}`);
|
||||
|
||||
if (rustDbExists) {
|
||||
// There was an existing rust database, so consider it healthy.
|
||||
return { exists: true, healthy: true };
|
||||
} else {
|
||||
// No rust store, so let's check if there is a legacy store not yet migrated.
|
||||
try {
|
||||
const legacyIdbExists = await IndexedDBCryptoStore.existsAndIsNotMigrated(
|
||||
getIDBFactory()!,
|
||||
LEGACY_CRYPTO_STORE_NAME,
|
||||
);
|
||||
log(`Legacy Crypto store using IndexedDB contains non migrated data? ${legacyIdbExists}`);
|
||||
return { exists: legacyIdbExists, healthy: true };
|
||||
} catch (e) {
|
||||
error("Legacy crypto store using IndexedDB inaccessible", e);
|
||||
}
|
||||
|
||||
// No need to check local storage or memory as rust stack doesn't support them.
|
||||
// Given that rust stack requires indexeddb, set healthy to false.
|
||||
return { exists: false, healthy: false };
|
||||
if (rustDbExists) {
|
||||
// There was an existing rust database, so consider it healthy.
|
||||
return { exists: true, healthy: true };
|
||||
} else {
|
||||
// No rust store, so let's check if there is a legacy store not yet migrated.
|
||||
try {
|
||||
const legacyIdbExists = await IndexedDBCryptoStore.existsAndIsNotMigrated(
|
||||
getIDBFactory()!,
|
||||
LEGACY_CRYPTO_STORE_NAME,
|
||||
);
|
||||
log(`Legacy Crypto store using IndexedDB contains non migrated data? ${legacyIdbExists}`);
|
||||
return { exists: legacyIdbExists, healthy: true };
|
||||
} catch (e) {
|
||||
error("Legacy crypto store using IndexedDB inaccessible", e);
|
||||
}
|
||||
} catch (e) {
|
||||
error("Rust crypto store using IndexedDB inaccessible", e);
|
||||
|
||||
// No need to check local storage or memory as rust stack doesn't support them.
|
||||
// Given that rust stack requires indexeddb, set healthy to false.
|
||||
return { exists: false, healthy: false };
|
||||
}
|
||||
} else {
|
||||
let exists = false;
|
||||
// legacy checks
|
||||
try {
|
||||
exists = await IndexedDBCryptoStore.exists(getIDBFactory()!, LEGACY_CRYPTO_STORE_NAME);
|
||||
log(`Crypto store using IndexedDB contains data? ${exists}`);
|
||||
return { exists, healthy: true };
|
||||
} catch (e) {
|
||||
error("Crypto store using IndexedDB inaccessible", e);
|
||||
}
|
||||
try {
|
||||
exists = LocalStorageCryptoStore.exists(localStorage);
|
||||
log(`Crypto store using local storage contains data? ${exists}`);
|
||||
return { exists, healthy: true };
|
||||
} catch (e) {
|
||||
error("Crypto store using local storage inaccessible", e);
|
||||
}
|
||||
log("Crypto store using memory only");
|
||||
return { exists, healthy: false };
|
||||
} catch (e) {
|
||||
error("Rust crypto store using IndexedDB inaccessible", e);
|
||||
return { exists: false, healthy: false };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user