Merge pull request #3720 from matrix-org/jryans/4s-new-key-backup
Create new key backups using secret storagepull/21833/head
commit
b7fe06706d
|
@ -19,13 +19,28 @@ import sdk from './index';
|
|||
import MatrixClientPeg from './MatrixClientPeg';
|
||||
import { deriveKey } from 'matrix-js-sdk/lib/crypto/key_passphrase';
|
||||
import { decodeRecoveryKey } from 'matrix-js-sdk/lib/crypto/recoverykey';
|
||||
import { _t } from './languageHandler';
|
||||
|
||||
export const getSecretStorageKey = async ({ keys: keyInfos }) => {
|
||||
// This stores the secret storage private keys in memory for the JS SDK. This is
|
||||
// only meant to act as a cache to avoid prompting the user multiple times
|
||||
// during the same single operation. Use `accessSecretStorage` below to scope a
|
||||
// single secret storage operation, as it will clear the cached keys once the
|
||||
// operation ends.
|
||||
let secretStorageKeys = {};
|
||||
let cachingAllowed = false;
|
||||
|
||||
async function getSecretStorageKey({ keys: keyInfos }) {
|
||||
const keyInfoEntries = Object.entries(keyInfos);
|
||||
if (keyInfoEntries.length > 1) {
|
||||
throw new Error("Multiple storage key requests not implemented");
|
||||
}
|
||||
const [name, info] = keyInfoEntries[0];
|
||||
|
||||
// Check the in-memory cache
|
||||
if (cachingAllowed && secretStorageKeys[name]) {
|
||||
return [name, secretStorageKeys[name]];
|
||||
}
|
||||
|
||||
const inputToKey = async ({ passphrase, recoveryKey }) => {
|
||||
if (passphrase) {
|
||||
return deriveKey(
|
||||
|
@ -54,5 +69,81 @@ export const getSecretStorageKey = async ({ keys: keyInfos }) => {
|
|||
throw new Error("Secret storage access canceled");
|
||||
}
|
||||
const key = await inputToKey(input);
|
||||
|
||||
// Save to cache to avoid future prompts in the current session
|
||||
if (cachingAllowed) {
|
||||
secretStorageKeys[name] = key;
|
||||
}
|
||||
|
||||
return [name, key];
|
||||
}
|
||||
|
||||
export const crossSigningCallbacks = {
|
||||
getSecretStorageKey,
|
||||
};
|
||||
|
||||
/**
|
||||
* This helper should be used whenever you need to access secret storage. It
|
||||
* ensures that secret storage (and also cross-signing since they each depend on
|
||||
* each other in a cycle of sorts) have been bootstrapped before running the
|
||||
* provided function.
|
||||
*
|
||||
* Bootstrapping secret storage may take one of these paths:
|
||||
* 1. Create secret storage from a passphrase and store cross-signing keys
|
||||
* in secret storage.
|
||||
* 2. Access existing secret storage by requesting passphrase and accessing
|
||||
* cross-signing keys as needed.
|
||||
* 3. All keys are loaded and there's nothing to do.
|
||||
*
|
||||
* Additionally, the secret storage keys are cached during the scope of this function
|
||||
* to ensure the user is prompted only once for their secret storage
|
||||
* passphrase. The cache is then
|
||||
*
|
||||
* @param {Function} [func] An operation to perform once secret storage has been
|
||||
* bootstrapped. Optional.
|
||||
*/
|
||||
export async function accessSecretStorage(func = async () => { }) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
cachingAllowed = true;
|
||||
|
||||
try {
|
||||
if (!cli.hasSecretStorageKey()) {
|
||||
// This dialog calls bootstrap itself after guiding the user through
|
||||
// passphrase creation.
|
||||
const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
|
||||
import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"),
|
||||
null, null, /* priority = */ false, /* static = */ true,
|
||||
);
|
||||
const [confirmed] = await finished;
|
||||
if (!confirmed) {
|
||||
throw new Error("Secret storage creation canceled");
|
||||
}
|
||||
} else {
|
||||
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
||||
await cli.bootstrapSecretStorage({
|
||||
authUploadDeviceSigningKeys: async (makeRequest) => {
|
||||
const { finished } = Modal.createTrackedDialog(
|
||||
'Cross-signing keys dialog', '', InteractiveAuthDialog,
|
||||
{
|
||||
title: _t("Send cross-signing keys to homeserver"),
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
makeRequest,
|
||||
},
|
||||
);
|
||||
const [confirmed] = await finished;
|
||||
if (!confirmed) {
|
||||
throw new Error("Cross-signing key upload auth canceled");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// `return await` needed here to ensure `finally` block runs after the
|
||||
// inner operation completes.
|
||||
return await func();
|
||||
} finally {
|
||||
// Clear secret storage key cache now that work is complete
|
||||
cachingAllowed = false;
|
||||
secretStorageKeys = {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ import {verificationMethods} from 'matrix-js-sdk/lib/crypto';
|
|||
import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientBackedSettingsHandler";
|
||||
import * as StorageManager from './utils/StorageManager';
|
||||
import IdentityAuthClient from './IdentityAuthClient';
|
||||
import * as CrossSigningManager from './CrossSigningManager';
|
||||
import { crossSigningCallbacks } from './CrossSigningManager';
|
||||
|
||||
interface MatrixClientCreds {
|
||||
homeserverUrl: string,
|
||||
|
@ -224,7 +224,7 @@ class MatrixClientPeg {
|
|||
|
||||
opts.cryptoCallbacks = {};
|
||||
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
Object.assign(opts.cryptoCallbacks, CrossSigningManager);
|
||||
Object.assign(opts.cryptoCallbacks, crossSigningCallbacks);
|
||||
}
|
||||
|
||||
this.matrixClient = createMatrixClient(opts);
|
||||
|
|
|
@ -16,12 +16,11 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import FileSaver from 'file-saver';
|
||||
|
||||
import sdk from '../../../../index';
|
||||
import MatrixClientPeg from '../../../../MatrixClientPeg';
|
||||
import { scorePassword } from '../../../../utils/PasswordScorer';
|
||||
|
||||
import FileSaver from 'file-saver';
|
||||
|
||||
import { _t } from '../../../../languageHandler';
|
||||
|
||||
const PHASE_PASSPHRASE = 0;
|
||||
|
@ -118,7 +117,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
|||
phase: PHASE_DONE,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("Error creating key backup", e);
|
||||
console.error("Error creating key backup", e);
|
||||
// TODO: If creating a version succeeds, but backup fails, should we
|
||||
// delete the version, disable backup, or do nothing? If we just
|
||||
// disable without deleting, we'll enable on next app reload since
|
||||
|
|
|
@ -19,7 +19,7 @@ import React from 'react';
|
|||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import sdk from '../../../index';
|
||||
import Modal from '../../../Modal';
|
||||
import { accessSecretStorage } from '../../../CrossSigningManager';
|
||||
|
||||
export default class CrossSigningPanel extends React.PureComponent {
|
||||
constructor(props) {
|
||||
|
@ -36,6 +36,8 @@ export default class CrossSigningPanel extends React.PureComponent {
|
|||
componentDidMount() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli.on("accountData", this.onAccountData);
|
||||
cli.on("userTrustStatusChanged", this.onStatusChanged);
|
||||
cli.on("crossSigning.keysChanged", this.onStatusChanged);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -43,6 +45,8 @@ export default class CrossSigningPanel extends React.PureComponent {
|
|||
const cli = MatrixClientPeg.get();
|
||||
if (!cli) return;
|
||||
cli.removeListener("accountData", this.onAccountData);
|
||||
cli.removeListener("userTrustStatusChanged", this.onStatusChanged);
|
||||
cli.removeListener("crossSigning.keysChanged", this.onStatusChanged);
|
||||
}
|
||||
|
||||
onAccountData = (event) => {
|
||||
|
@ -52,6 +56,10 @@ export default class CrossSigningPanel extends React.PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
onStatusChanged = () => {
|
||||
this.setState(this._getUpdatedStatus());
|
||||
};
|
||||
|
||||
_getUpdatedStatus() {
|
||||
// XXX: Add public accessors if we keep this around in production
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
@ -78,38 +86,8 @@ export default class CrossSigningPanel extends React.PureComponent {
|
|||
*/
|
||||
_bootstrapSecureSecretStorage = async () => {
|
||||
this.setState({ error: null });
|
||||
const cli = MatrixClientPeg.get();
|
||||
try {
|
||||
if (!cli.hasSecretStorageKey()) {
|
||||
// This dialog calls bootstrap itself after guiding the user through
|
||||
// passphrase creation.
|
||||
const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
|
||||
import("../../../async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"),
|
||||
null, null, /* priority = */ false, /* static = */ true,
|
||||
);
|
||||
const [confirmed] = await finished;
|
||||
if (!confirmed) {
|
||||
throw new Error("Secret storage creation canceled");
|
||||
}
|
||||
} else {
|
||||
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
||||
await cli.bootstrapSecretStorage({
|
||||
authUploadDeviceSigningKeys: async (makeRequest) => {
|
||||
const { finished } = Modal.createTrackedDialog(
|
||||
'Cross-signing keys dialog', '', InteractiveAuthDialog,
|
||||
{
|
||||
title: _t("Send cross-signing keys to homeserver"),
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
makeRequest,
|
||||
},
|
||||
);
|
||||
const [confirmed] = await finished;
|
||||
if (!confirmed) {
|
||||
throw new Error("Cross-signing key upload auth canceled");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
await accessSecretStorage();
|
||||
} catch (e) {
|
||||
this.setState({ error: e });
|
||||
console.error("Error bootstrapping secret storage", e);
|
||||
|
@ -132,28 +110,59 @@ export default class CrossSigningPanel extends React.PureComponent {
|
|||
errorSection = <div className="error">{error.toString()}</div>;
|
||||
}
|
||||
|
||||
const enabled = (
|
||||
crossSigningPublicKeysOnDevice &&
|
||||
crossSigningPrivateKeysInStorage &&
|
||||
secretStorageKeyInAccount
|
||||
);
|
||||
|
||||
let summarisedStatus;
|
||||
if (enabled) {
|
||||
summarisedStatus = <p>✅ {_t(
|
||||
"Cross-signing and secret storage are enabled.",
|
||||
)}</p>;
|
||||
} else if (crossSigningPrivateKeysInStorage) {
|
||||
summarisedStatus = <p>{_t(
|
||||
"Your account has a cross-signing identity in secret storage, but it " +
|
||||
"is not yet trusted by this device.",
|
||||
)}</p>;
|
||||
} else {
|
||||
summarisedStatus = <p>{_t(
|
||||
"Cross-signing and secret storage are not yet set up.",
|
||||
)}</p>;
|
||||
}
|
||||
|
||||
let bootstrapButton;
|
||||
if (!enabled) {
|
||||
bootstrapButton = <div className="mx_CrossSigningPanel_buttonRow">
|
||||
<AccessibleButton kind="primary" onClick={this._bootstrapSecureSecretStorage}>
|
||||
{_t("Bootstrap cross-signing and secret storage")}
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table className="mx_CrossSigningPanel_statusList"><tbody>
|
||||
<tr>
|
||||
<td>{_t("Cross-signing public keys:")}</td>
|
||||
<td>{crossSigningPublicKeysOnDevice ? _t("on device") : _t("not found")}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{_t("Cross-signing private keys:")}</td>
|
||||
<td>{crossSigningPrivateKeysInStorage ? _t("in secret storage") : _t("not found")}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{_t("Secret storage public key:")}</td>
|
||||
<td>{secretStorageKeyInAccount ? _t("in account data") : _t("not found")}</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<div className="mx_CrossSigningPanel_buttonRow">
|
||||
<AccessibleButton kind="primary" onClick={this._bootstrapSecureSecretStorage}>
|
||||
{_t("Bootstrap Secure Secret Storage")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
{summarisedStatus}
|
||||
<details>
|
||||
<summary>{_t("Advanced")}</summary>
|
||||
<table className="mx_CrossSigningPanel_statusList"><tbody>
|
||||
<tr>
|
||||
<td>{_t("Cross-signing public keys:")}</td>
|
||||
<td>{crossSigningPublicKeysOnDevice ? _t("on device") : _t("not found")}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{_t("Cross-signing private keys:")}</td>
|
||||
<td>{crossSigningPrivateKeysInStorage ? _t("in secret storage") : _t("not found")}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{_t("Secret storage public key:")}</td>
|
||||
<td>{secretStorageKeyInAccount ? _t("in account data") : _t("not found")}</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</details>
|
||||
{errorSection}
|
||||
{bootstrapButton}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ import sdk from '../../../index';
|
|||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Modal from '../../../Modal';
|
||||
import SettingsStore from '../../../../lib/settings/SettingsStore';
|
||||
import { accessSecretStorage } from '../../../CrossSigningManager';
|
||||
|
||||
export default class KeyBackupPanel extends React.PureComponent {
|
||||
constructor(props) {
|
||||
|
@ -31,6 +33,8 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
loading: true,
|
||||
error: null,
|
||||
backupInfo: null,
|
||||
backupSigStatus: null,
|
||||
backupKeyStored: null,
|
||||
sessionsRemaining: 0,
|
||||
};
|
||||
}
|
||||
|
@ -72,9 +76,11 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
async _checkKeyBackupStatus() {
|
||||
try {
|
||||
const {backupInfo, trustInfo} = await MatrixClientPeg.get().checkKeyBackup();
|
||||
const backupKeyStored = await MatrixClientPeg.get().isKeyBackupKeyStored();
|
||||
this.setState({
|
||||
backupInfo,
|
||||
backupSigStatus: trustInfo,
|
||||
backupKeyStored,
|
||||
error: null,
|
||||
loading: false,
|
||||
});
|
||||
|
@ -85,6 +91,7 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
error: e,
|
||||
backupInfo: null,
|
||||
backupSigStatus: null,
|
||||
backupKeyStored: null,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
@ -95,11 +102,13 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
try {
|
||||
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||
const backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo);
|
||||
const backupKeyStored = await MatrixClientPeg.get().isKeyBackupKeyStored();
|
||||
if (this._unmounted) return;
|
||||
this.setState({
|
||||
error: null,
|
||||
backupInfo,
|
||||
backupSigStatus,
|
||||
backupKeyStored,
|
||||
loading: false,
|
||||
});
|
||||
} catch (e) {
|
||||
|
@ -109,6 +118,7 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
error: e,
|
||||
backupInfo: null,
|
||||
backupSigStatus: null,
|
||||
backupKeyStored: null,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
@ -125,6 +135,31 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
_startNewBackupWithSecureSecretStorage = async () => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
let info;
|
||||
try {
|
||||
await accessSecretStorage(async () => {
|
||||
info = await cli.prepareKeyBackupVersion(
|
||||
null /* random key */,
|
||||
{ secureSecretStorage: true },
|
||||
);
|
||||
info = await cli.createKeyBackupVersion(info);
|
||||
});
|
||||
await MatrixClientPeg.get().scheduleAllGroupSessionsForBackup();
|
||||
this._loadBackupStatus();
|
||||
} catch (e) {
|
||||
console.error("Error creating key backup", e);
|
||||
// TODO: If creating a version succeeds, but backup fails, should we
|
||||
// delete the version, disable backup, or do nothing? If we just
|
||||
// disable without deleting, we'll enable on next app reload since
|
||||
// it is trusted.
|
||||
if (info && info.version) {
|
||||
MatrixClientPeg.get().deleteKeyBackupVersion(info.version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_deleteBackup = () => {
|
||||
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
|
||||
Modal.createTrackedDialog('Delete Backup', '', QuestionDialog, {
|
||||
|
@ -145,10 +180,23 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
_restoreBackup = () => {
|
||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
||||
Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
|
||||
});
|
||||
_restoreBackup = async () => {
|
||||
// Use legacy path if backup key not stored in secret storage
|
||||
if (!this.state.backupKeyStored) {
|
||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
||||
Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await accessSecretStorage(async () => {
|
||||
await MatrixClientPeg.get().restoreKeyBackupWithSecretStorage(
|
||||
this.state.backupInfo,
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("Error restoring backup", e);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -193,6 +241,13 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
restoreButtonCaption = _t("Connect this device to Key Backup");
|
||||
}
|
||||
|
||||
let keyStatus;
|
||||
if (this.state.backupKeyStored === true) {
|
||||
keyStatus = _t("in secret storage");
|
||||
} else {
|
||||
keyStatus = _t("not stored");
|
||||
}
|
||||
|
||||
let uploadStatus;
|
||||
const { sessionsRemaining } = this.state;
|
||||
if (!MatrixClientPeg.get().getKeyBackupEnabled()) {
|
||||
|
@ -223,10 +278,29 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
sig.device &&
|
||||
sig.device.getFingerprint() === MatrixClientPeg.get().getDeviceEd25519Key()
|
||||
);
|
||||
const fromThisUser = (
|
||||
sig.crossSigningId &&
|
||||
sig.deviceId === MatrixClientPeg.get().getCrossSigningId()
|
||||
);
|
||||
let sigStatus;
|
||||
if (!sig.device) {
|
||||
if (sig.valid && fromThisUser) {
|
||||
sigStatus = _t(
|
||||
"Backup has a signature from <verify>unknown</verify> device with ID %(deviceId)s.",
|
||||
"Backup has a <validity>valid</validity> signature from this user",
|
||||
{}, { validity },
|
||||
);
|
||||
} else if (!sig.valid && fromThisUser) {
|
||||
sigStatus = _t(
|
||||
"Backup has a <validity>invalid</validity> signature from this user",
|
||||
{}, { validity },
|
||||
);
|
||||
} else if (sig.crossSigningId) {
|
||||
sigStatus = _t(
|
||||
"Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s",
|
||||
{ deviceId: sig.deviceId }, { verify },
|
||||
);
|
||||
} else if (!sig.device) {
|
||||
sigStatus = _t(
|
||||
"Backup has a signature from <verify>unknown</verify> device with ID %(deviceId)s",
|
||||
{ deviceId: sig.deviceId }, { verify },
|
||||
);
|
||||
} else if (sig.valid && fromThisDevice) {
|
||||
|
@ -279,26 +353,54 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
trustedLocally = _t("This backup is trusted because it has been restored on this device");
|
||||
}
|
||||
|
||||
let buttonRow = (
|
||||
<div className="mx_KeyBackupPanel_buttonRow">
|
||||
<AccessibleButton kind="primary" onClick={this._restoreBackup}>
|
||||
{restoreButtonCaption}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton kind="danger" onClick={this._deleteBackup}>
|
||||
{_t("Delete Backup")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
if (this.state.backupKeyStored && !SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
buttonRow = <p>⚠️ {_t(
|
||||
"Backup key stored in secret storage, but this feature is not " +
|
||||
"enabled on this device. Please enable cross-signing in Labs to " +
|
||||
"modify key backup state.",
|
||||
)}</p>;
|
||||
}
|
||||
|
||||
return <div>
|
||||
<div>{clientBackupStatus}</div>
|
||||
<details>
|
||||
<summary>{_t("Advanced")}</summary>
|
||||
<div>{_t("Backup version: ")}{this.state.backupInfo.version}</div>
|
||||
<div>{_t("Algorithm: ")}{this.state.backupInfo.algorithm}</div>
|
||||
<div>{_t("Backup key stored: ")}{keyStatus}</div>
|
||||
{uploadStatus}
|
||||
<div>{backupSigStatuses}</div>
|
||||
<div>{trustedLocally}</div>
|
||||
</details>
|
||||
<div className="mx_KeyBackupPanel_buttonRow">
|
||||
<AccessibleButton kind="primary" onClick={this._restoreBackup}>
|
||||
{restoreButtonCaption}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton kind="danger" onClick={this._deleteBackup}>
|
||||
{ _t("Delete Backup") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
{buttonRow}
|
||||
</div>;
|
||||
} else {
|
||||
// This is a temporary button for testing the new path which stores
|
||||
// the key backup key in SSSS. Initialising SSSS depends on
|
||||
// cross-signing and is part of the same project, so we only show
|
||||
// this mode when the cross-signing feature is enabled.
|
||||
// TODO: Clean this up when removing the feature flag.
|
||||
let secureSecretStorageKeyBackup;
|
||||
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
secureSecretStorageKeyBackup = (
|
||||
<div className="mx_KeyBackupPanel_buttonRow">
|
||||
<AccessibleButton kind="primary" onClick={this._startNewBackupWithSecureSecretStorage}>
|
||||
{_t("Start using Key Backup with Secure Secret Storage")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div>
|
||||
<div>
|
||||
<p>{_t(
|
||||
|
@ -313,6 +415,7 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
{_t("Start using Key Backup")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
{secureSecretStorageKeyBackup}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
|
||||
"The server does not support the room version specified.": "The server does not support the room version specified.",
|
||||
"Failure to create room": "Failure to create room",
|
||||
"Send cross-signing keys to homeserver": "Send cross-signing keys to homeserver",
|
||||
"Send anyway": "Send anyway",
|
||||
"Send": "Send",
|
||||
"Sun": "Sun",
|
||||
|
@ -512,7 +513,10 @@
|
|||
"New Password": "New Password",
|
||||
"Confirm password": "Confirm password",
|
||||
"Change Password": "Change Password",
|
||||
"Send cross-signing keys to homeserver": "Send cross-signing keys to homeserver",
|
||||
"Cross-signing and secret storage are enabled.": "Cross-signing and secret storage are enabled.",
|
||||
"Your account has a cross-signing identity in secret storage, but it is not yet trusted by this device.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this device.",
|
||||
"Cross-signing and secret storage are not yet set up.": "Cross-signing and secret storage are not yet set up.",
|
||||
"Bootstrap cross-signing and secret storage": "Bootstrap cross-signing and secret storage",
|
||||
"Cross-signing public keys:": "Cross-signing public keys:",
|
||||
"on device": "on device",
|
||||
"not found": "not found",
|
||||
|
@ -520,7 +524,6 @@
|
|||
"in secret storage": "in secret storage",
|
||||
"Secret storage public key:": "Secret storage public key:",
|
||||
"in account data": "in account data",
|
||||
"Bootstrap Secure Secret Storage": "Bootstrap Secure Secret Storage",
|
||||
"Your homeserver does not support device management.": "Your homeserver does not support device management.",
|
||||
"Unable to load device list": "Unable to load device list",
|
||||
"Authentication": "Authentication",
|
||||
|
@ -544,9 +547,13 @@
|
|||
"This device is <b>not backing up your keys</b>, but you do have an existing backup you can restore from and add to going forward.": "This device is <b>not backing up your keys</b>, but you do have an existing backup you can restore from and add to going forward.",
|
||||
"Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.": "Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.",
|
||||
"Connect this device to Key Backup": "Connect this device to Key Backup",
|
||||
"not stored": "not stored",
|
||||
"Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...",
|
||||
"All keys backed up": "All keys backed up",
|
||||
"Backup has a signature from <verify>unknown</verify> device with ID %(deviceId)s.": "Backup has a signature from <verify>unknown</verify> device with ID %(deviceId)s.",
|
||||
"Backup has a <validity>valid</validity> signature from this user": "Backup has a <validity>valid</validity> signature from this user",
|
||||
"Backup has a <validity>invalid</validity> signature from this user": "Backup has a <validity>invalid</validity> signature from this user",
|
||||
"Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s": "Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s",
|
||||
"Backup has a signature from <verify>unknown</verify> device with ID %(deviceId)s": "Backup has a signature from <verify>unknown</verify> device with ID %(deviceId)s",
|
||||
"Backup has a <validity>valid</validity> signature from this device": "Backup has a <validity>valid</validity> signature from this device",
|
||||
"Backup has an <validity>invalid</validity> signature from this device": "Backup has an <validity>invalid</validity> signature from this device",
|
||||
"Backup has a <validity>valid</validity> signature from <verify>verified</verify> device <device></device>": "Backup has a <validity>valid</validity> signature from <verify>verified</verify> device <device></device>",
|
||||
|
@ -555,8 +562,11 @@
|
|||
"Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> device <device></device>": "Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> device <device></device>",
|
||||
"Backup is not signed by any of your devices": "Backup is not signed by any of your devices",
|
||||
"This backup is trusted because it has been restored on this device": "This backup is trusted because it has been restored on this device",
|
||||
"Backup key stored in secret storage, but this feature is not enabled on this device. Please enable cross-signing in Labs to modify key backup state.": "Backup key stored in secret storage, but this feature is not enabled on this device. Please enable cross-signing in Labs to modify key backup state.",
|
||||
"Backup version: ": "Backup version: ",
|
||||
"Algorithm: ": "Algorithm: ",
|
||||
"Backup key stored: ": "Backup key stored: ",
|
||||
"Start using Key Backup with Secure Secret Storage": "Start using Key Backup with Secure Secret Storage",
|
||||
"Your keys are <b>not being backed up from this device</b>.": "Your keys are <b>not being backed up from this device</b>.",
|
||||
"Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.",
|
||||
"Start using Key Backup": "Start using Key Backup",
|
||||
|
|
Loading…
Reference in New Issue