Use private key check to provide feedback

pull/21833/head
J. Ryan Stinnett 2019-12-06 17:54:00 +00:00
parent 9b9e074d30
commit 24d6e7e456
3 changed files with 71 additions and 18 deletions

View File

@ -16,6 +16,7 @@ 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';
@ -39,40 +40,49 @@ export const saveCrossSigningKeys = newKeys => Object.assign(crossSigningKeys, n
// there.
const secretStorageKeys = {};
// XXX: This flow should maybe be reworked to allow retries in case of typos,
// etc.
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];
// Check the in-memory cache
if (secretStorageKeys[name]) {
return [name, secretStorageKeys[name]];
}
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,
},
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");
}
let key;
if (input.passphrase) {
key = await deriveKey(
input.passphrase,
info.passphrase.salt,
info.passphrase.iterations,
);
} else {
key = decodeRecoveryKey(input.recoveryKey);
}
const key = await inputToKey(input);
// Save to cache to avoid future prompts in the current session
secretStorageKeys[name] = key;
return [name, key];
};

View File

@ -30,6 +30,8 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
static propTypes = {
// { passphrase, pubkey }
keyInfo: PropTypes.object.isRequired,
// Function from one of { passphrase, recoveryKey } -> boolean
checkPrivateKey: PropTypes.func.isRequired,
}
constructor(props) {
@ -39,6 +41,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
recoveryKeyValid: false,
forceRecoveryKey: false,
passPhrase: '',
keyMatches: null,
};
}
@ -61,25 +64,41 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
this.setState({
recoveryKey: e.target.value,
recoveryKeyValid: MatrixClientPeg.get().isValidRecoveryKey(e.target.value),
keyMatches: null,
});
}
_onPassPhraseNext = async () => {
this.props.onFinished({ passphrase: this.state.passPhrase });
this.setState({ keyMatches: null });
const input = { passphrase: this.state.passPhrase };
const keyMatches = await this.props.checkPrivateKey(input);
if (keyMatches) {
this.props.onFinished(input);
} else {
this.setState({ keyMatches });
}
}
_onRecoveryKeyNext = async () => {
this.props.onFinished({ recoveryKey: this.state.recoveryKey });
this.setState({ keyMatches: null });
const input = { recoveryKey: this.state.recoveryKey };
const keyMatches = await this.props.checkPrivateKey(input);
if (keyMatches) {
this.props.onFinished(input);
} else {
this.setState({ keyMatches });
}
}
_onPassPhraseChange = (e) => {
this.setState({
passPhrase: e.target.value,
keyMatches: null,
});
}
_onPassPhraseKeyPress = (e) => {
if (e.key === Key.ENTER) {
if (e.key === Key.ENTER && this.state.passPhrase.length > 0) {
this._onPassPhraseNext();
}
}
@ -106,6 +125,19 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
title = _t("Enter secret storage passphrase");
let keyStatus;
if (this.state.keyMatches === false) {
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
{"\uD83D\uDC4E "}{_t(
"Unable to access secret storage. Please verify that you " +
"entered the correct passphrase.",
)}
</div>;
} else {
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus"></div>;
}
content = <div>
<p>{_t(
"<b>Warning</b>: You should only access secret storage " +
@ -125,11 +157,13 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
value={this.state.passPhrase}
autoFocus={true}
/>
{keyStatus}
<DialogButtons primaryButton={_t('Next')}
onPrimaryButtonClick={this._onPassPhraseNext}
hasCancel={true}
onCancel={this._onCancel}
focus={false}
primaryDisabled={this.state.passPhrase.length === 0}
/>
</div>
{_t(
@ -163,6 +197,13 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
{"\uD83D\uDC4D "}{_t("This looks like a valid recovery key!")}
</div>;
} else if (this.state.keyMatches === false) {
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
{"\uD83D\uDC4E "}{_t(
"Unable to access secret storage. Please verify that you " +
"entered the correct recovery key.",
)}
</div>;
} else {
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
{"\uD83D\uDC4E "}{_t("Not a valid recovery key")}

View File

@ -1518,11 +1518,13 @@
"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.",
"<b>Warning</b>: You should only access secret storage from a trusted computer.": "<b>Warning</b>: 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 <button1>use your recovery key</button1> or <button2>set up new recovery options</button2>.": "If you've forgotten your passphrase you can <button1>use your recovery key</button1> or <button2>set up new recovery options</button2>.",
"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 <button>set up new recovery options</button>.": "If you've forgotten your recovery key you can <button>set up new recovery options</button>.",