diff --git a/res/css/_components.scss b/res/css/_components.scss
index 4081e49630..529ce9ac85 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -64,7 +64,6 @@
@import "./views/dialogs/_GroupAddressPicker.scss";
@import "./views/dialogs/_IncomingSasDialog.scss";
@import "./views/dialogs/_MessageEditHistoryDialog.scss";
-@import "./views/dialogs/_RestoreKeyBackupDialog.scss";
@import "./views/dialogs/_RoomSettingsDialog.scss";
@import "./views/dialogs/_RoomUpgradeDialog.scss";
@import "./views/dialogs/_RoomUpgradeWarningDialog.scss";
@@ -83,6 +82,8 @@
@import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss";
@import "./views/dialogs/keybackup/_KeyBackupFailedDialog.scss";
@import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss";
+@import "./views/dialogs/secretstorage/_AccessSecretStorageDialog.scss";
+@import "./views/dialogs/secretstorage/_CreateSecretStorageDialog.scss";
@import "./views/directory/_NetworkDropdown.scss";
@import "./views/elements/_AccessibleButton.scss";
@import "./views/elements/_AddressSelector.scss";
@@ -174,6 +175,7 @@
@import "./views/rooms/_Stickers.scss";
@import "./views/rooms/_TopUnreadMessagesBar.scss";
@import "./views/rooms/_WhoIsTypingTile.scss";
+@import "./views/settings/_CrossSigningPanel.scss";
@import "./views/settings/_DevicesPanel.scss";
@import "./views/settings/_EmailAddresses.scss";
@import "./views/settings/_IntegrationManager.scss";
diff --git a/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss b/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss
index 415a2021cc..9cba8e0da9 100644
--- a/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss
+++ b/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss
@@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
+Copyright 2019 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.
@@ -14,6 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+.mx_RestoreKeyBackupDialog_keyStatus {
+ height: 30px;
+}
+
.mx_RestoreKeyBackupDialog_primaryContainer {
/* FIXME: plinth colour in new theme(s). background-color: $accent-color; */
padding: 20px;
diff --git a/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss
new file mode 100644
index 0000000000..db11e91bdb
--- /dev/null
+++ b/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss
@@ -0,0 +1,34 @@
+/*
+Copyright 2018 New Vector Ltd
+Copyright 2019 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.
+*/
+
+.mx_AccessSecretStorageDialog_keyStatus {
+ height: 30px;
+}
+
+.mx_AccessSecretStorageDialog_primaryContainer {
+ /* FIXME: plinth colour in new theme(s). background-color: $accent-color; */
+ padding: 20px;
+}
+
+.mx_AccessSecretStorageDialog_passPhraseInput,
+.mx_AccessSecretStorageDialog_recoveryKeyInput {
+ width: 300px;
+ border: 1px solid $accent-color;
+ border-radius: 5px;
+ padding: 10px;
+}
+
diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss
new file mode 100644
index 0000000000..757d8028f0
--- /dev/null
+++ b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss
@@ -0,0 +1,88 @@
+/*
+Copyright 2018 New Vector Ltd
+Copyright 2019 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.
+*/
+
+.mx_CreateSecretStorageDialog .mx_Dialog_title {
+ /* TODO: Consider setting this for all dialog titles. */
+ margin-bottom: 1em;
+}
+
+.mx_CreateSecretStorageDialog_primaryContainer {
+ /* FIXME: plinth colour in new theme(s). background-color: $accent-color; */
+ padding: 20px;
+}
+
+.mx_CreateSecretStorageDialog_primaryContainer::after {
+ content: "";
+ clear: both;
+ display: block;
+}
+
+.mx_CreateSecretStorageDialog_passPhraseContainer {
+ display: flex;
+ align-items: start;
+}
+
+.mx_CreateSecretStorageDialog_passPhraseHelp {
+ flex: 1;
+ height: 85px;
+ margin-left: 20px;
+ font-size: 80%;
+}
+
+.mx_CreateSecretStorageDialog_passPhraseHelp progress {
+ width: 100%;
+}
+
+.mx_CreateSecretStorageDialog_passPhraseInput {
+ flex: none;
+ width: 250px;
+ border: 1px solid $accent-color;
+ border-radius: 5px;
+ padding: 10px;
+ margin-bottom: 1em;
+}
+
+.mx_CreateSecretStorageDialog_passPhraseMatch {
+ margin-left: 20px;
+}
+
+.mx_CreateSecretStorageDialog_recoveryKeyHeader {
+ margin-bottom: 1em;
+}
+
+.mx_CreateSecretStorageDialog_recoveryKeyContainer {
+ display: flex;
+}
+
+.mx_CreateSecretStorageDialog_recoveryKey {
+ width: 262px;
+ padding: 20px;
+ color: $info-plinth-fg-color;
+ background-color: $info-plinth-bg-color;
+ margin-right: 12px;
+}
+
+.mx_CreateSecretStorageDialog_recoveryKeyButtons {
+ flex: 1;
+ display: flex;
+ align-items: center;
+}
+
+.mx_CreateSecretStorageDialog_recoveryKeyButtons button {
+ flex: 1;
+ white-space: nowrap;
+}
diff --git a/res/css/views/dialogs/_RestoreKeyBackupDialog.scss b/res/css/views/settings/_CrossSigningPanel.scss
similarity index 66%
rename from res/css/views/dialogs/_RestoreKeyBackupDialog.scss
rename to res/css/views/settings/_CrossSigningPanel.scss
index 69e00c416a..fa9f76a963 100644
--- a/res/css/views/dialogs/_RestoreKeyBackupDialog.scss
+++ b/res/css/views/settings/_CrossSigningPanel.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2018 New Vector Ltd
+Copyright 2019 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.
@@ -14,6 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-.mx_RestoreKeyBackupDialog_keyStatus {
- height: 30px;
+.mx_CrossSigningPanel_statusList {
+ border-spacing: 0;
+
+ td {
+ padding: 0;
+
+ &:first-of-type {
+ padding-inline-end: 1em;
+ }
+ }
+}
+
+.mx_CrossSigningPanel_buttonRow {
+ margin: 1em 0;
}
diff --git a/res/css/views/settings/_KeyBackupPanel.scss b/res/css/views/settings/_KeyBackupPanel.scss
index 1bcc0ab10d..872162caad 100644
--- a/res/css/views/settings/_KeyBackupPanel.scss
+++ b/res/css/views/settings/_KeyBackupPanel.scss
@@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
+Copyright 2019 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.
@@ -30,3 +31,7 @@ limitations under the License.
.mx_KeyBackupPanel_deviceName {
font-style: italic;
}
+
+.mx_KeyBackupPanel_buttonRow {
+ margin: 1em 0;
+}
diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js
new file mode 100644
index 0000000000..5dc709bd10
--- /dev/null
+++ b/src/CrossSigningManager.js
@@ -0,0 +1,58 @@
+/*
+Copyright 2019 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 Modal from './Modal';
+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';
+
+export const getSecretStorageKey = async ({ 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];
+ const inputToKey = async ({ passphrase, recoveryKey }) => {
+ if (passphrase) {
+ return deriveKey(
+ passphrase,
+ info.passphrase.salt,
+ info.passphrase.iterations,
+ );
+ } else {
+ return decodeRecoveryKey(recoveryKey);
+ }
+ };
+ const AccessSecretStorageDialog =
+ sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog");
+ const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "",
+ AccessSecretStorageDialog,
+ {
+ keyInfo: info,
+ checkPrivateKey: async (input) => {
+ const key = await inputToKey(input);
+ return MatrixClientPeg.get().checkSecretStoragePrivateKey(key, info.pubkey);
+ },
+ },
+ );
+ const [input] = await finished;
+ if (!input) {
+ throw new Error("Secret storage access canceled");
+ }
+ const key = await inputToKey(input);
+ return [name, key];
+};
diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js
index ef0130ec15..a3a0588bfc 100644
--- a/src/MatrixClientPeg.js
+++ b/src/MatrixClientPeg.js
@@ -1,7 +1,8 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd.
-Copyright 2017 New Vector Ltd
+Copyright 2017, 2018, 2019 New Vector Ltd
+Copyright 2019 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.
@@ -30,6 +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';
interface MatrixClientCreds {
homeserverUrl: string,
@@ -220,14 +222,9 @@ class MatrixClientPeg {
identityServer: new IdentityAuthClient(),
};
+ opts.cryptoCallbacks = {};
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
- // TODO: Cross-signing keys are temporarily in memory only. A
- // separate task in the cross-signing project will build from here.
- const keys = [];
- opts.cryptoCallbacks = {
- getCrossSigningKey: k => keys[k],
- saveCrossSigningKeys: newKeys => Object.assign(keys, newKeys),
- };
+ Object.assign(opts.cryptoCallbacks, CrossSigningManager);
}
this.matrixClient = createMatrixClient(opts);
diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js
index 4953cdff68..eae102196f 100644
--- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js
+++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js
@@ -1,5 +1,6 @@
/*
Copyright 2018, 2019 New Vector Ltd
+Copyright 2019 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.
@@ -15,7 +16,6 @@ limitations under the License.
*/
import React from 'react';
-import createReactClass from 'create-react-class';
import sdk from '../../../../index';
import MatrixClientPeg from '../../../../MatrixClientPeg';
import { scorePassword } from '../../../../utils/PasswordScorer';
@@ -45,13 +45,15 @@ function selectText(target) {
selection.addRange(range);
}
-/**
+/*
* Walks the user through the process of creating an e2e key backup
* on the server.
*/
-export default createReactClass({
- getInitialState: function() {
- return {
+export default class CreateKeyBackupDialog extends React.PureComponent {
+ constructor(props) {
+ super(props);
+
+ this.state = {
phase: PHASE_PASSPHRASE,
passPhrase: '',
passPhraseConfirm: '',
@@ -60,25 +62,25 @@ export default createReactClass({
zxcvbnResult: null,
setPassPhrase: false,
};
- },
+ }
- componentWillMount: function() {
+ componentWillMount() {
this._recoveryKeyNode = null;
this._keyBackupInfo = null;
this._setZxcvbnResultTimeout = null;
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
if (this._setZxcvbnResultTimeout !== null) {
clearTimeout(this._setZxcvbnResultTimeout);
}
- },
+ }
- _collectRecoveryKeyNode: function(n) {
+ _collectRecoveryKeyNode = (n) => {
this._recoveryKeyNode = n;
- },
+ }
- _onCopyClick: function() {
+ _onCopyClick = () => {
selectText(this._recoveryKeyNode);
const successful = document.execCommand('copy');
if (successful) {
@@ -87,9 +89,9 @@ export default createReactClass({
phase: PHASE_KEEPITSAFE,
});
}
- },
+ }
- _onDownloadClick: function() {
+ _onDownloadClick = () => {
const blob = new Blob([this._keyBackupInfo.recovery_key], {
type: 'text/plain;charset=us-ascii',
});
@@ -99,9 +101,9 @@ export default createReactClass({
downloaded: true,
phase: PHASE_KEEPITSAFE,
});
- },
+ }
- _createBackup: async function() {
+ _createBackup = async () => {
this.setState({
phase: PHASE_BACKINGUP,
error: null,
@@ -128,38 +130,38 @@ export default createReactClass({
error: e,
});
}
- },
+ }
- _onCancel: function() {
+ _onCancel = () => {
this.props.onFinished(false);
- },
+ }
- _onDone: function() {
+ _onDone = () => {
this.props.onFinished(true);
- },
+ }
- _onOptOutClick: function() {
+ _onOptOutClick = () => {
this.setState({phase: PHASE_OPTOUT_CONFIRM});
- },
+ }
- _onSetUpClick: function() {
+ _onSetUpClick = () => {
this.setState({phase: PHASE_PASSPHRASE});
- },
+ }
- _onSkipPassPhraseClick: async function() {
+ _onSkipPassPhraseClick = async () => {
this._keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion();
this.setState({
copied: false,
downloaded: false,
phase: PHASE_SHOWKEY,
});
- },
+ }
- _onPassPhraseNextClick: function() {
+ _onPassPhraseNextClick = () => {
this.setState({phase: PHASE_PASSPHRASE_CONFIRM});
- },
+ }
- _onPassPhraseKeyPress: async function(e) {
+ _onPassPhraseKeyPress = async (e) => {
if (e.key === 'Enter') {
// If we're waiting for the timeout before updating the result at this point,
// skip ahead and do it now, otherwise we'll deny the attempt to proceed
@@ -177,9 +179,9 @@ export default createReactClass({
this._onPassPhraseNextClick();
}
}
- },
+ }
- _onPassPhraseConfirmNextClick: async function() {
+ _onPassPhraseConfirmNextClick = async () => {
this._keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion(this.state.passPhrase);
this.setState({
setPassPhrase: true,
@@ -187,30 +189,30 @@ export default createReactClass({
downloaded: false,
phase: PHASE_SHOWKEY,
});
- },
+ }
- _onPassPhraseConfirmKeyPress: function(e) {
+ _onPassPhraseConfirmKeyPress = (e) => {
if (e.key === 'Enter' && this.state.passPhrase === this.state.passPhraseConfirm) {
this._onPassPhraseConfirmNextClick();
}
- },
+ }
- _onSetAgainClick: function() {
+ _onSetAgainClick = () => {
this.setState({
passPhrase: '',
passPhraseConfirm: '',
phase: PHASE_PASSPHRASE,
zxcvbnResult: null,
});
- },
+ }
- _onKeepItSafeBackClick: function() {
+ _onKeepItSafeBackClick = () => {
this.setState({
phase: PHASE_SHOWKEY,
});
- },
+ }
- _onPassPhraseChange: function(e) {
+ _onPassPhraseChange = (e) => {
this.setState({
passPhrase: e.target.value,
});
@@ -227,19 +229,19 @@ export default createReactClass({
zxcvbnResult: scorePassword(this.state.passPhrase),
});
}, PASSPHRASE_FEEDBACK_DELAY);
- },
+ }
- _onPassPhraseConfirmChange: function(e) {
+ _onPassPhraseConfirmChange = (e) => {
this.setState({
passPhraseConfirm: e.target.value,
});
- },
+ }
- _passPhraseIsValid: function() {
+ _passPhraseIsValid() {
return this.state.zxcvbnResult && this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE;
- },
+ }
- _renderPhasePassPhrase: function() {
+ _renderPhasePassPhrase() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
let strengthMeter;
@@ -266,7 +268,7 @@ export default createReactClass({
return
{_t(
- "Warning: you should only set up key backup from a trusted computer.", {},
+ "Warning: You should only set up key backup from a trusted computer.", {},
{ b: sub => {sub} },
)}
;
- },
+ }
- _renderPhasePassPhraseConfirm: function() {
+ _renderPhasePassPhraseConfirm() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let matchText;
@@ -361,9 +363,9 @@ export default createReactClass({
disabled={this.state.passPhrase !== this.state.passPhraseConfirm}
/>
;
- },
+ }
- _renderPhaseShowKey: function() {
+ _renderPhaseShowKey() {
let bodyText;
if (this.state.setPassPhrase) {
bodyText = _t(
@@ -380,7 +382,7 @@ export default createReactClass({
"access to your encrypted messages if you forget your passphrase.",
)}
{_t(
- "Keep your recovery key somewhere very secure, like a password manager (or a safe)",
+ "Keep your recovery key somewhere very secure, like a password manager (or a safe).",
)}
{_t(
+ "Warning: You should only set up secret storage from a trusted computer.", {},
+ { b: sub => {sub} },
+ )}
+
{_t(
+ "We'll use secret storage to optionally store an encrypted copy of " +
+ "your cross-signing identity for verifying other devices and message " +
+ "keys on our server. Protect your access to encrypted messages with a " +
+ "passphrase to keep it secure.",
+ )}
+
{_t("For maximum security, this should be different from your account password.")}
+
+
+
+
+
+ {strengthMeter}
+ {helpText}
+
+
+
+
+
+
+
+ {_t("Advanced")}
+
+
+
;
+ }
+
+ _renderPhasePassPhraseConfirm() {
+ const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
+
+ let matchText;
+ if (this.state.passPhraseConfirm === this.state.passPhrase) {
+ matchText = _t("That matches!");
+ } else if (!this.state.passPhrase.startsWith(this.state.passPhraseConfirm)) {
+ // only tell them they're wrong if they've actually gone wrong.
+ // Security concious readers will note that if you left riot-web unattended
+ // on this screen, this would make it easy for a malicious person to guess
+ // your passphrase one letter at a time, but they could get this faster by
+ // just opening the browser's developer tools and reading it.
+ // Note that not having typed anything at all will not hit this clause and
+ // fall through so empty box === no hint.
+ matchText = _t("That doesn't match.");
+ }
+
+ let passPhraseMatch = null;
+ if (matchText) {
+ passPhraseMatch =
{_t(
+ "Please enter your passphrase a second time to confirm.",
+ )}
+
+
+
+
+
+ {passPhraseMatch}
+
+
+
+
;
+ }
+
+ _renderPhaseShowKey() {
+ let bodyText;
+ if (this.state.setPassPhrase) {
+ bodyText = _t(
+ "As a safety net, you can use it to restore your access to encrypted " +
+ "messages if you forget your passphrase.",
+ );
+ } else {
+ bodyText = _t(
+ "As a safety net, you can use it to restore your access to encrypted " +
+ "messages.",
+ );
+ }
+
+ return
+
{_t(
+ "Your recovery key is a safety net - you can use it to restore " +
+ "access to your encrypted messages if you forget your passphrase.",
+ )}
+
{_t(
+ "Keep your recovery key somewhere very secure, like a password manager (or a safe).",
+ )}
+
{bodyText}
+
+
+ {_t("Your Recovery Key")}
+
+
+
+ {this._encodedRecoveryKey}
+
+
+
+
+
+
+
+
;
+ }
+
+ _renderPhaseKeepItSafe() {
+ let introText;
+ if (this.state.copied) {
+ introText = _t(
+ "Your recovery key has been copied to your clipboard, paste it to:",
+ {}, {b: s => {s}},
+ );
+ } else if (this.state.downloaded) {
+ introText = _t(
+ "Your recovery key is in your Downloads folder.",
+ {}, {b: s => {s}},
+ );
+ }
+ const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
+ return
+ {introText}
+
+
{_t("Print it and store it somewhere safe", {}, {b: s => {s}})}
+
{_t("Save it on a USB key or backup drive", {}, {b: s => {s}})}
+
{_t("Copy it to your personal cloud storage", {}, {b: s => {s}})}
+ {_t(
+ "Without setting up secret storage, you won't be able to restore your " +
+ "access to encrypted messages or your cross-signing identity for " +
+ "verifying other devices if you log out or use another device.",
+ )}
+
+
+
+
;
+ }
+
+ _titleForPhase(phase) {
+ switch (phase) {
+ case PHASE_PASSPHRASE:
+ return _t('Secure your encrypted messages with a passphrase');
+ case PHASE_PASSPHRASE_CONFIRM:
+ return _t('Confirm your passphrase');
+ case PHASE_OPTOUT_CONFIRM:
+ return _t('Warning!');
+ case PHASE_SHOWKEY:
+ return _t('Recovery key');
+ case PHASE_KEEPITSAFE:
+ return _t('Keep it safe');
+ case PHASE_STORING:
+ return _t('Storing secrets...');
+ case PHASE_DONE:
+ return _t('Success!');
+ default:
+ return null;
+ }
+ }
+
+ render() {
+ const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
+
+ let content;
+ if (this.state.error) {
+ const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
+ content =
{_t(
- "Warning: you should only set up key backup " +
+ "Warning: You should only set up key backup " +
"from a trusted computer.", {},
{ b: sub => {sub} },
)}
{_t("Back up your keys before signing out to avoid losing them.")}
-
- { _t("Start using Key Backup") }
-
+
+
+ {_t("Start using Key Backup")}
+
+
;
}
}
diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
index 0732bcf926..98ec18df5a 100644
--- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
@@ -17,7 +17,7 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../../../languageHandler";
-import {SettingLevel} from "../../../../../settings/SettingsStore";
+import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore";
import MatrixClientPeg from "../../../../../MatrixClientPeg";
import * as FormattingUtils from "../../../../../utils/FormattingUtils";
import AccessibleButton from "../../../elements/AccessibleButton";
@@ -252,6 +252,23 @@ export default class SecurityUserSettingsTab extends React.Component {
);
+ // XXX: There's no such panel in the current cross-signing designs, but
+ // it's useful to have for testing the feature. If there's no interest
+ // in having advanced details here once all flows are implemented, we
+ // can remove this.
+ const CrossSigningPanel = sdk.getComponent('views.settings.CrossSigningPanel');
+ let crossSigning;
+ if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
+ crossSigning = (
+
{_t("Analytics")}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index d1bd34860d..7cf0aeaf36 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -495,6 +495,15 @@
"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 public keys:": "Cross-signing public keys:",
+ "on device": "on device",
+ "not found": "not found",
+ "Cross-signing private keys:": "Cross-signing private keys:",
+ "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",
@@ -696,6 +705,7 @@
"Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites",
"Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
"Key backup": "Key backup",
+ "Cross-signing": "Cross-signing",
"Security & Privacy": "Security & Privacy",
"Devices": "Devices",
"A device's public name is visible to people you communicate with": "A device's public name is visible to people you communicate with",
@@ -1514,6 +1524,17 @@
"Remember my selection for this widget": "Remember my selection for this widget",
"Allow": "Allow",
"Deny": "Deny",
+ "Enter secret storage passphrase": "Enter secret storage passphrase",
+ "Unable to access secret storage. Please verify that you entered the correct passphrase.": "Unable to access secret storage. Please verify that you entered the correct passphrase.",
+ "Warning: You should only access secret storage from a trusted computer.": "Warning: You should only access secret storage from a trusted computer.",
+ "Access your secure message history and your cross-signing identity for verifying other devices by entering your passphrase.": "Access your secure message history and your cross-signing identity for verifying other devices by entering your passphrase.",
+ "If you've forgotten your passphrase you can use your recovery key or set up new recovery options.": "If you've forgotten your passphrase you can use your recovery key or set up new recovery options.",
+ "Enter secret storage recovery key": "Enter secret storage recovery key",
+ "This looks like a valid recovery key!": "This looks like a valid recovery key!",
+ "Unable to access secret storage. Please verify that you entered the correct recovery key.": "Unable to access secret storage. Please verify that you entered the correct recovery key.",
+ "Not a valid recovery key": "Not a valid recovery key",
+ "Access your secure message history and your cross-signing identity for verifying other devices by entering your recovery key.": "Access your secure message history and your cross-signing identity for verifying other devices by entering your recovery key.",
+ "If you've forgotten your recovery key you can .": "If you've forgotten your recovery key you can .",
"Unable to load backup status": "Unable to load backup status",
"Recovery Key Mismatch": "Recovery Key Mismatch",
"Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.",
@@ -1529,10 +1550,9 @@
"Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Access your secure message history and set up secure messaging by entering your recovery passphrase.",
"If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options",
"Enter Recovery Key": "Enter Recovery Key",
- "This looks like a valid recovery key!": "This looks like a valid recovery key!",
- "Not a valid recovery key": "Not a valid recovery key",
+ "Warning: You should only set up key backup from a trusted computer.": "Warning: You should only set up key backup from a trusted computer.",
"Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.",
- "If you've forgotten your recovery passphrase you can ": "If you've forgotten your recovery passphrase you can ",
+ "If you've forgotten your recovery key you can ": "If you've forgotten your recovery key you can ",
"Private Chat": "Private Chat",
"Public Chat": "Public Chat",
"Custom": "Custom",
@@ -1884,39 +1904,50 @@
"File to import": "File to import",
"Import": "Import",
"Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.",
- "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.",
+ "Warning: You should only set up secret storage from a trusted computer.": "Warning: You should only set up secret storage from a trusted computer.",
+ "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.": "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.",
"For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.",
"Enter a passphrase...": "Enter a passphrase...",
- "Set up with a Recovery Key": "Set up with a Recovery Key",
+ "Set up with a recovery key": "Set up with a recovery key",
"That matches!": "That matches!",
"That doesn't match.": "That doesn't match.",
"Go back to set it again.": "Go back to set it again.",
"Please enter your passphrase a second time to confirm.": "Please enter your passphrase a second time to confirm.",
"Repeat your passphrase...": "Repeat your passphrase...",
- "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.": "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.",
- "As a safety net, you can use it to restore your encrypted message history.": "As a safety net, you can use it to restore your encrypted message history.",
+ "As a safety net, you can use it to restore your access to encrypted messages if you forget your passphrase.": "As a safety net, you can use it to restore your access to encrypted messages if you forget your passphrase.",
+ "As a safety net, you can use it to restore your access to encrypted messages.": "As a safety net, you can use it to restore your access to encrypted messages.",
"Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.",
- "Keep your recovery key somewhere very secure, like a password manager (or a safe)": "Keep your recovery key somewhere very secure, like a password manager (or a safe)",
+ "Keep your recovery key somewhere very secure, like a password manager (or a safe).": "Keep your recovery key somewhere very secure, like a password manager (or a safe).",
"Your Recovery Key": "Your Recovery Key",
"Copy to clipboard": "Copy to clipboard",
"Download": "Download",
- "Your Recovery Key has been copied to your clipboard, paste it to:": "Your Recovery Key has been copied to your clipboard, paste it to:",
- "Your Recovery Key is in your Downloads folder.": "Your Recovery Key is in your Downloads folder.",
+ "Your recovery key has been copied to your clipboard, paste it to:": "Your recovery key has been copied to your clipboard, paste it to:",
+ "Your recovery key is in your Downloads folder.": "Your recovery key is in your Downloads folder.",
"Print it and store it somewhere safe": "Print it and store it somewhere safe",
"Save it on a USB key or backup drive": "Save it on a USB key or backup drive",
"Copy it to your personal cloud storage": "Copy it to your personal cloud storage",
+ "Your access to encrypted messages is now protected.": "Your access to encrypted messages is now protected.",
+ "Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.": "Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.",
+ "Set up secret storage": "Set up secret storage",
+ "Secure your encrypted messages with a passphrase": "Secure your encrypted messages with a passphrase",
+ "Confirm your passphrase": "Confirm your passphrase",
+ "Recovery key": "Recovery key",
+ "Keep it safe": "Keep it safe",
+ "Storing secrets...": "Storing secrets...",
+ "Success!": "Success!",
+ "Unable to set up secret storage": "Unable to set up secret storage",
+ "Retry": "Retry",
+ "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.",
+ "Set up with a Recovery Key": "Set up with a Recovery Key",
+ "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.": "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.",
+ "As a safety net, you can use it to restore your encrypted message history.": "As a safety net, you can use it to restore your encrypted message history.",
"Your keys are being backed up (the first backup could take a few minutes).": "Your keys are being backed up (the first backup could take a few minutes).",
"Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.",
"Set up Secure Message Recovery": "Set up Secure Message Recovery",
"Secure your backup with a passphrase": "Secure your backup with a passphrase",
- "Confirm your passphrase": "Confirm your passphrase",
- "Recovery key": "Recovery key",
- "Keep it safe": "Keep it safe",
"Starting backup...": "Starting backup...",
- "Success!": "Success!",
"Create Key Backup": "Create Key Backup",
"Unable to create key backup": "Unable to create key backup",
- "Retry": "Retry",
"Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.",
"If you don't want to set this up now, you can later in Settings.": "If you don't want to set this up now, you can later in Settings.",
"Set up": "Set up",