Use private key check to provide feedback
parent
9b9e074d30
commit
24d6e7e456
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import sdk from './index';
|
import sdk from './index';
|
||||||
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
import { deriveKey } from 'matrix-js-sdk/lib/crypto/key_passphrase';
|
import { deriveKey } from 'matrix-js-sdk/lib/crypto/key_passphrase';
|
||||||
import { decodeRecoveryKey } from 'matrix-js-sdk/lib/crypto/recoverykey';
|
import { decodeRecoveryKey } from 'matrix-js-sdk/lib/crypto/recoverykey';
|
||||||
|
|
||||||
|
@ -39,40 +40,49 @@ export const saveCrossSigningKeys = newKeys => Object.assign(crossSigningKeys, n
|
||||||
// there.
|
// there.
|
||||||
const secretStorageKeys = {};
|
const secretStorageKeys = {};
|
||||||
|
|
||||||
// XXX: This flow should maybe be reworked to allow retries in case of typos,
|
|
||||||
// etc.
|
|
||||||
export const getSecretStorageKey = async ({ keys: keyInfos }) => {
|
export const getSecretStorageKey = async ({ keys: keyInfos }) => {
|
||||||
const keyInfoEntries = Object.entries(keyInfos);
|
const keyInfoEntries = Object.entries(keyInfos);
|
||||||
if (keyInfoEntries.length > 1) {
|
if (keyInfoEntries.length > 1) {
|
||||||
throw new Error("Multiple storage key requests not implemented");
|
throw new Error("Multiple storage key requests not implemented");
|
||||||
}
|
}
|
||||||
const [name, info] = keyInfoEntries[0];
|
const [name, info] = keyInfoEntries[0];
|
||||||
|
|
||||||
// Check the in-memory cache
|
// Check the in-memory cache
|
||||||
if (secretStorageKeys[name]) {
|
if (secretStorageKeys[name]) {
|
||||||
return [name, 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 =
|
const AccessSecretStorageDialog =
|
||||||
sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog");
|
sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog");
|
||||||
const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "",
|
const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "",
|
||||||
AccessSecretStorageDialog, {
|
AccessSecretStorageDialog,
|
||||||
keyInfo: info,
|
{
|
||||||
},
|
keyInfo: info,
|
||||||
|
checkPrivateKey: async (input) => {
|
||||||
|
const key = await inputToKey(input);
|
||||||
|
return MatrixClientPeg.get().checkSecretStoragePrivateKey(key, info.pubkey);
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
const [input] = await finished;
|
const [input] = await finished;
|
||||||
if (!input) {
|
if (!input) {
|
||||||
throw new Error("Secret storage access canceled");
|
throw new Error("Secret storage access canceled");
|
||||||
}
|
}
|
||||||
let key;
|
const key = await inputToKey(input);
|
||||||
if (input.passphrase) {
|
|
||||||
key = await deriveKey(
|
|
||||||
input.passphrase,
|
|
||||||
info.passphrase.salt,
|
|
||||||
info.passphrase.iterations,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
key = decodeRecoveryKey(input.recoveryKey);
|
|
||||||
}
|
|
||||||
// Save to cache to avoid future prompts in the current session
|
// Save to cache to avoid future prompts in the current session
|
||||||
secretStorageKeys[name] = key;
|
secretStorageKeys[name] = key;
|
||||||
|
|
||||||
return [name, key];
|
return [name, key];
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,6 +30,8 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// { passphrase, pubkey }
|
// { passphrase, pubkey }
|
||||||
keyInfo: PropTypes.object.isRequired,
|
keyInfo: PropTypes.object.isRequired,
|
||||||
|
// Function from one of { passphrase, recoveryKey } -> boolean
|
||||||
|
checkPrivateKey: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -39,6 +41,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
recoveryKeyValid: false,
|
recoveryKeyValid: false,
|
||||||
forceRecoveryKey: false,
|
forceRecoveryKey: false,
|
||||||
passPhrase: '',
|
passPhrase: '',
|
||||||
|
keyMatches: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,25 +64,41 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
this.setState({
|
this.setState({
|
||||||
recoveryKey: e.target.value,
|
recoveryKey: e.target.value,
|
||||||
recoveryKeyValid: MatrixClientPeg.get().isValidRecoveryKey(e.target.value),
|
recoveryKeyValid: MatrixClientPeg.get().isValidRecoveryKey(e.target.value),
|
||||||
|
keyMatches: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPassPhraseNext = async () => {
|
_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 () => {
|
_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) => {
|
_onPassPhraseChange = (e) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhrase: e.target.value,
|
passPhrase: e.target.value,
|
||||||
|
keyMatches: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPassPhraseKeyPress = (e) => {
|
_onPassPhraseKeyPress = (e) => {
|
||||||
if (e.key === Key.ENTER) {
|
if (e.key === Key.ENTER && this.state.passPhrase.length > 0) {
|
||||||
this._onPassPhraseNext();
|
this._onPassPhraseNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,6 +125,19 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
title = _t("Enter secret storage passphrase");
|
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>
|
content = <div>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"<b>Warning</b>: You should only access secret storage " +
|
"<b>Warning</b>: You should only access secret storage " +
|
||||||
|
@ -125,11 +157,13 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
value={this.state.passPhrase}
|
value={this.state.passPhrase}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
/>
|
/>
|
||||||
|
{keyStatus}
|
||||||
<DialogButtons primaryButton={_t('Next')}
|
<DialogButtons primaryButton={_t('Next')}
|
||||||
onPrimaryButtonClick={this._onPassPhraseNext}
|
onPrimaryButtonClick={this._onPassPhraseNext}
|
||||||
hasCancel={true}
|
hasCancel={true}
|
||||||
onCancel={this._onCancel}
|
onCancel={this._onCancel}
|
||||||
focus={false}
|
focus={false}
|
||||||
|
primaryDisabled={this.state.passPhrase.length === 0}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{_t(
|
{_t(
|
||||||
|
@ -163,6 +197,13 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
|
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
|
||||||
{"\uD83D\uDC4D "}{_t("This looks like a valid recovery key!")}
|
{"\uD83D\uDC4D "}{_t("This looks like a valid recovery key!")}
|
||||||
</div>;
|
</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 {
|
} else {
|
||||||
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
|
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
|
||||||
{"\uD83D\uDC4E "}{_t("Not a valid recovery key")}
|
{"\uD83D\uDC4E "}{_t("Not a valid recovery key")}
|
||||||
|
|
|
@ -1518,11 +1518,13 @@
|
||||||
"Allow": "Allow",
|
"Allow": "Allow",
|
||||||
"Deny": "Deny",
|
"Deny": "Deny",
|
||||||
"Enter secret storage passphrase": "Enter secret storage passphrase",
|
"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.",
|
"<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.",
|
"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>.",
|
"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",
|
"Enter secret storage recovery key": "Enter secret storage recovery key",
|
||||||
"This looks like a valid recovery key!": "This looks like a valid 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",
|
"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.",
|
"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>.",
|
"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>.",
|
||||||
|
|
Loading…
Reference in New Issue