diff --git a/res/css/_components.scss b/res/css/_components.scss index 9796b59213..b1fbe30f13 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -81,6 +81,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"; 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/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index a65ebbb763..d73931f57b 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -30,6 +30,8 @@ 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 { deriveKey } from 'matrix-js-sdk/lib/crypto/key_passphrase'; +import { decodeRecoveryKey } from 'matrix-js-sdk/lib/crypto/recoverykey'; interface MatrixClientCreds { homeserverUrl: string, @@ -224,13 +226,41 @@ class MatrixClientPeg { // This stores the cross-signing private keys in memory for the JS SDK. They // are also persisted to Secure Secret Storage in account data by // the JS SDK when created. - // XXX: On desktop platforms, we plan to store only the SSSS default - // key in a secure enclave, while the cross-signing private keys - // will still be retrieved from SSSS, so it's unclear that we - // actually need these cross-signing application callbacks for Riot. - // Should the JS SDK default to in-memory storage of these itself? const keys = {}; opts.cryptoCallbacks = { + // XXX: This flow should maybe be reworked to allow retries in + // case of typos, etc. + getSecretStorageKey: async 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 AccessSecretStorageDialog = + sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog"); + const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "", + AccessSecretStorageDialog, { + keyInfo: info, + }, + ); + const [input] = await finished; + if (!input) { + throw new Error("Secret storage access canceled"); + } + let key; + const { passphrase } = info; + if (passphrase) { + key = await deriveKey(input, passphrase.salt, passphrase.iterations); + } else { + key = decodeRecoveryKey(input); + } + return [name, key]; + }, + // XXX: On desktop platforms, we plan to store only the SSSS default + // key in a secure enclave, while the cross-signing private keys + // will still be retrieved from SSSS, so it's unclear that we + // actually need these cross-signing application callbacks for Riot. + // Should the JS SDK default to in-memory storage of these itself? getCrossSigningKey: k => keys[k], saveCrossSigningKeys: newKeys => Object.assign(keys, newKeys), }; diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index ba75032ea4..eae102196f 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -268,7 +268,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { 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} }, )}
{_t( @@ -382,7 +382,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { "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).", )}
{bodyText}
{_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.")}
+ +{_t( + "Please enter your passphrase a second time to confirm.", + )}
+{_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}
+{this._encodedRecoveryKey}
+ {_t( + "Your access to encrypted messages is now protected.", + )}
+{_t("Unable to set up secret storage")}
+{_t( - "Warning: you should only set up key backup " + + "Warning: You should only set up key backup " + "from a trusted computer.", {}, { b: sub => {sub} }, )}
@@ -322,7 +322,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { />{_t( + "Warning: You should only access secret storage " + + "from a trusted computer.", {}, + { b: sub => {sub} }, + )}
+{_t( + "Access your secure message history and your cross-signing " + + "identity for verifying other devices by entering your passphrase.", + )}
+ +{_t( + "Warning: You should only access secret storage " + + "from a trusted computer.", {}, + { b: sub => {sub} }, + )}
+{_t( + "Access your secure message history and your cross-signing " + + "identity for verifying other devices by entering your recovery key.", + )}
+ +