diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js
index 9a77901d2e..0cf67a3551 100644
--- a/src/MatrixClientPeg.js
+++ b/src/MatrixClientPeg.js
@@ -29,6 +29,7 @@ import SettingsStore from './settings/SettingsStore';
import MatrixActionCreators from './actions/MatrixActionCreators';
import {phasedRollOutExpiredForUser} from "./PhasedRollOut";
import Modal from './Modal';
+import {verificationMethods} from 'matrix-js-sdk/lib/crypto';
interface MatrixClientCreds {
homeserverUrl: string,
@@ -184,6 +185,7 @@ class MatrixClientPeg {
deviceId: creds.deviceId,
timelineSupport: true,
forceTURN: SettingsStore.getValue('webRtcForceTURN', false),
+ verificationMethods: [verificationMethods.SAS]
};
this.matrixClient = createMatrixClient(opts, useIndexedDb);
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 2d1c928494..7167e50fb2 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -1277,6 +1277,7 @@ export default React.createClass({
this.firstSyncComplete = false;
this.firstSyncPromise = Promise.defer();
const cli = MatrixClientPeg.get();
+ const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');
// Allow the JS SDK to reap timeline events. This reduces the amount of
// memory consumed as the JS SDK stores multiple distinct copies of room
@@ -1476,6 +1477,12 @@ export default React.createClass({
}
});
+ cli.on("crypto.verification.start", (verifier) => {
+ Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
+ verifier,
+ });
+ });
+
// Fire the tinter right on startup to ensure the default theme is applied
// A later sync can/will correct the tint to be the right value for the user
const colorScheme = SettingsStore.getValue("roomColor");
diff --git a/src/components/views/dialogs/DeviceVerifyDialog.js b/src/components/views/dialogs/DeviceVerifyDialog.js
index 6bec933389..169dc26c52 100644
--- a/src/components/views/dialogs/DeviceVerifyDialog.js
+++ b/src/components/views/dialogs/DeviceVerifyDialog.js
@@ -1,6 +1,7 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
+Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -21,58 +22,273 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
import sdk from '../../../index';
import * as FormattingUtils from '../../../utils/FormattingUtils';
import { _t } from '../../../languageHandler';
+import SettingsStore from '../../../settings/SettingsStore';
+import {verificationMethods} from 'matrix-js-sdk/lib/crypto';
+import {renderSasWaitAccept} from '../../../sas_ui';
-export default function DeviceVerifyDialog(props) {
- const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
+const MODE_LEGACY = 'legacy';
+const MODE_SAS = 'sas';
- const key = FormattingUtils.formatCryptoKey(props.device.getFingerprint());
- const body = (
-
-
- { _t("To verify that this device can be trusted, please contact its " +
- "owner using some other means (e.g. in person or a phone call) " +
- "and ask them whether the key they see in their User Settings " +
- "for this device matches the key below:") }
-
-
-
-
{ props.device.getDisplayName() }
-
{ props.device.deviceId }
-
{ key }
-
-
-
- { _t("If it matches, press the verify button below. " +
- "If it doesn't, then someone else is intercepting this device " +
- "and you probably want to press the blacklist button instead.") }
-
-
- { _t("In future this verification process will be more sophisticated.") }
-
+ { _t("To verify that this device can be trusted, please contact its " +
+ "owner using some other means (e.g. in person or a phone call) " +
+ "and ask them whether the key they see in their User Settings " +
+ "for this device matches the key below:") }
+
+
+
+
{ this.props.device.getDisplayName() }
+
{ this.props.device.deviceId }
+
{ key }
+
+
+
+ { _t("If it matches, press the verify button below. " +
+ "If it doesn't, then someone else is intercepting this device " +
+ "and you probably want to press the blacklist button instead.") }
+
+
+ { _t("In future this verification process will be more sophisticated.") }
+
;
+ }
+ }
}
-DeviceVerifyDialog.propTypes = {
- userId: PropTypes.string.isRequired,
- device: PropTypes.object.isRequired,
- onFinished: PropTypes.func.isRequired,
-};
diff --git a/src/components/views/dialogs/IncomingSasDialog.js b/src/components/views/dialogs/IncomingSasDialog.js
new file mode 100644
index 0000000000..732ce2a461
--- /dev/null
+++ b/src/components/views/dialogs/IncomingSasDialog.js
@@ -0,0 +1,218 @@
+/*
+Copyright 2019 New Vector Ltd
+
+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 React from 'react';
+import PropTypes from 'prop-types';
+import MatrixClientPeg from '../../../MatrixClientPeg';
+import sdk from '../../../index';
+import { _t } from '../../../languageHandler';
+import {verificationMethods} from 'matrix-js-sdk/lib/crypto';
+
+const PHASE_START = 0;
+const PHASE_SHOW_SAS = 1;
+const PHASE_WAIT_FOR_PARTNER_TO_CONFIRM = 2;
+const PHASE_VERIFIED = 3;
+const PHASE_CANCELLED = 4;
+
+export default class IncomingSasDialog extends React.Component {
+ static propTypes = {
+ verifier: PropTypes.object.isRequired,
+ };
+
+ constructor(props) {
+ super(props);
+
+ this._showSasEvent = null;
+ this.state = {
+ phase: PHASE_START,
+ };
+ this.props.verifier.on('show_sas', this._onVerifierShowSas);
+ this.props.verifier.on('cancel', this._onVerifierCancel);
+ }
+
+ componentWillUnmount() {
+ if (this.state.phase !== PHASE_CANCELLED && this.state.phase !== PHASE_VERIFIED) {
+ this.props.verifier.cancel('User cancel');
+ }
+ this.props.verifier.removeListener('show_sas', this._onVerifierShowSas);
+ }
+
+ _onFinished = () => {
+ this.props.onFinished(this.state.phase === PHASE_VERIFIED);
+ }
+
+ _onCancelClick = () => {
+ this.props.onFinished(this.state.phase === PHASE_VERIFIED);
+ }
+
+ _onContinueClick = () => {
+ this.setState({phase: PHASE_WAIT_FOR_PARTNER_TO_CONFIRM});
+ this.props.verifier.verify().then(() => {
+ this.setState({phase: PHASE_VERIFIED});
+ }).catch((e) => {
+ console.log("Verification failed", e);
+ });
+ }
+
+ _onVerifierShowSas = (e) => {
+ this._showSasEvent = e;
+ this.setState({
+ phase: PHASE_SHOW_SAS,
+ sas: e.sas,
+ });
+ }
+
+ _onVerifierCancel = (e) => {
+ this.setState({
+ phase: PHASE_CANCELLED,
+ });
+ }
+
+ _onSasMatchesClick = () => {
+ this._showSasEvent.confirm();
+ this.setState({
+ phase: PHASE_WAIT_FOR_PARTNER_TO_CONFIRM,
+ });
+ }
+
+ _onVerifiedDoneClick = () => {
+ this.props.onFinished(true);
+ }
+
+ _renderPhaseStart() {
+ const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
+
+ return (
+
+
{this.props.verifier.userId}
+
{_t(
+ "Verify this user to mark them as trusted. " +
+ "Trusting users gives you extra peace of mind when using " +
+ "end-to-end encrypted messages."
+ )}
+
{_t(
+ // NB. Below wording adjusted to singular 'device' until we have
+ // cross-signing
+ "Verifying this user will mark their device as trusted, and " +
+ "also mark your device as trusted to them"
+ )}
{_t(
+ "The other party cancelled the verification."
+ )}
+
+
+ );
+ }
+
+ render() {
+ console.log("rendering pahse "+this.state.phase);
+ let body;
+ switch (this.state.phase) {
+ case PHASE_START:
+ body = this._renderPhaseStart();
+ break;
+ case PHASE_SHOW_SAS:
+ body = this._renderPhaseShowSas();
+ break;
+ case PHASE_WAIT_FOR_PARTNER_TO_CONFIRM:
+ body = this._renderPhaseWaitForPartnerToConfirm();
+ break;
+ case PHASE_VERIFIED:
+ body = this._renderPhaseVerified();
+ break;
+ case PHASE_CANCELLED:
+ body = this._renderPhaseCancelled();
+ break;
+ }
+
+ const BaseDialog = sdk.getComponent("dialogs.BaseDialog");
+ return (
+
+ {body}
+
+ );
+ }
+}
+
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 0fbed11e20..c26330dda4 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -114,6 +114,8 @@
"Failed to invite": "Failed to invite",
"Failed to invite users to the room:": "Failed to invite users to the room:",
"Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:",
+ "Waiting for %(userId)s to accept...": "Waiting for %(userId)s to accept...",
+ "Waiting for %(userId)s to confirm...": "Waiting for %(userId)s to confirm...",
"You need to be logged in.": "You need to be logged in.",
"You need to be able to invite users to do that.": "You need to be able to invite users to do that.",
"Unable to create widget.": "Unable to create widget.",
@@ -264,6 +266,7 @@
"Backup of encryption keys to server": "Backup of encryption keys to server",
"Render simple counters in room header": "Render simple counters in room header",
"Allow up to 6 rooms in a community to be shown simultaneously in a grid via the context menu": "Allow up to 6 rooms in a community to be shown simultaneously in a grid via the context menu",
+ "Two-way device verification using short text": "Two-way device verification using short text",
"Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing",
"Use compact timeline layout": "Use compact timeline layout",
"Hide removed messages": "Hide removed messages",
@@ -938,12 +941,22 @@
"Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)",
"To continue, please enter your password:": "To continue, please enter your password:",
"password": "password",
+ "Verify device": "Verify device",
+ "Use Legacy Verification (for older clients)": "Use Legacy Verification (for older clients)",
+ "Do clicky clicky button press request verify user send to do.": "Do clicky clicky button press request verify user send to do.",
+ "Send Verification Request": "Send Verification Request",
+ "Verify this user by confirming the following number appears on their screen": "Verify this user by confirming the following number appears on their screen",
+ "For maximum security, we reccommend you do this in person or use another trusted means of communication": "For maximum security, we reccommend you do this in person or use another trusted means of communication",
+ "This Matches": "This Matches",
+ "Verification complete!": "Verification complete!",
+ "Done": "Done",
+ "%(userId)s cancelled the verification.": "%(userId)s cancelled the verification.",
+ "Use two-way text verification": "Use two-way text verification",
"To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:",
"Device name": "Device name",
"Device key": "Device key",
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.",
"In future this verification process will be more sophisticated.": "In future this verification process will be more sophisticated.",
- "Verify device": "Verify device",
"I verify that the keys match": "I verify that the keys match",
"Back": "Back",
"Send Custom Event": "Send Custom Event",
@@ -960,6 +973,12 @@
"Toolbox": "Toolbox",
"Developer Tools": "Developer Tools",
"An error has occurred.": "An error has occurred.",
+ "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.",
+ "Verifying this user will mark their device as trusted, and also mark your device as trusted to them": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them",
+ "Waiting for partner to confirm...": "Waiting for partner to confirm...",
+ "Verification Complete!": "Verification Complete!",
+ "The other party cancelled the verification.": "The other party cancelled the verification.",
+ "Incoming Verification Request": "Incoming Verification Request",
"You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.",
"Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.",
"Start verification": "Start verification",
diff --git a/src/settings/Settings.js b/src/settings/Settings.js
index 9b46f9406b..9e8cd7a672 100644
--- a/src/settings/Settings.js
+++ b/src/settings/Settings.js
@@ -114,6 +114,12 @@ export const SETTINGS = {
supportedLevels: LEVELS_FEATURE,
default: false,
},
+ "feature_sas": {
+ isFeature: true,
+ displayName: _td("Two-way device verification using short text"),
+ supportedLevels: LEVELS_FEATURE,
+ default: false,
+ },
"MessageComposerInput.dontSuggestEmoji": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td('Disable Emoji suggestions while typing'),