From f77eb078498e1bdd761e176519fb5a5dc884529c Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 20 Jan 2020 15:16:41 +0000 Subject: [PATCH 1/6] Verify individual messages via cross-signing Fixes #11880 --- res/css/views/rooms/_EventTile.scss | 21 ++++++++-- res/themes/light/css/_light.scss | 1 + src/components/views/rooms/EventTile.js | 51 +++++++++++++++++++++---- 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index fbac1e932a..81ba547ff0 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -367,6 +367,11 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { opacity: 1; } +.mx_EventTile_e2eIcon_userVerified { + background-image: url('$(res)/img/e2e/normal.svg'); + opacity: 0.5; +} + .mx_EventTile_e2eIcon_unencrypted { background-image: url('$(res)/img/e2e/warning.svg'); opacity: 1; @@ -415,7 +420,8 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { } .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line, -.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line { +.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line, +.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line { padding-left: 60px; } @@ -427,8 +433,13 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { border-left: $e2e-unverified-color 5px solid; } +.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line { + border-left: $e2e-userVerified-color 5px solid; +} + .mx_EventTile:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line, -.mx_EventTile:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line { +.mx_EventTile:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line, +.mx_EventTile:hover.mx_EventTile_userVerified.mx_EventTile_info .mx_EventTile_line { padding-left: 78px; } @@ -439,14 +450,16 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { // Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies) .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > a > .mx_MessageTimestamp, -.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp { +.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp, +.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line > a > .mx_MessageTimestamp { left: 3px; width: auto; } // Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies) .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > .mx_EventTile_e2eIcon, -.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > .mx_EventTile_e2eIcon { +.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > .mx_EventTile_e2eIcon, +.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line > .mx_EventTile_e2eIcon { display: block; left: 41px; } diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 288fb3cadc..17b9a344ef 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -224,6 +224,7 @@ $copy-button-url: "$(res)/img/icon_copy_message.svg"; // e2e $e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color +$e2e-userVerified-color: #e8bf37; $e2e-unverified-color: #e8bf37; $e2e-warning-color: #ba6363; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index dce4dc8a93..4aefe6929b 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -235,6 +235,7 @@ export default createReactClass({ this._suppressReadReceiptAnimation = false; const client = this.context; client.on("deviceVerificationChanged", this.onDeviceVerificationChanged); + client.on("userTrustStatusChanged", this.onUserVerificationChanged); this.props.mxEvent.on("Event.decrypted", this._onDecrypted); if (this.props.showReactions) { this.props.mxEvent.on("Event.relationsCreated", this._onReactionsCreated); @@ -260,6 +261,7 @@ export default createReactClass({ componentWillUnmount: function() { const client = this.context; client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); + client.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); this.props.mxEvent.removeListener("Event.decrypted", this._onDecrypted); if (this.props.showReactions) { this.props.mxEvent.removeListener("Event.relationsCreated", this._onReactionsCreated); @@ -282,18 +284,42 @@ export default createReactClass({ } }, + onUserVerificationChanged: function(userId, _trustStatus) { + if (userId === this.props.mxEvent.getSender()) { + this._verifyEvent(this.props.mxEvent); + } + }, + _verifyEvent: async function(mxEvent) { if (!mxEvent.isEncrypted()) { return; } + // If we directly trust the device, short-circuit here const verified = await this.context.isEventSenderVerified(mxEvent); + if (verified) { + this.setState({ + verified: "verified" + }, () => { + // Decryption may have caused a change in size + this.props.onHeightChanged(); + }); + return; + } + + const eventSenderTrust = await this.context.checkEventSenderTrust(mxEvent); + if (!eventSenderTrust) { + // We cannot find the device. Instead, we have to verify the user. + const userTrust = await this.context.checkUserTrust(mxEvent.getSender()); + this.setState({ + verified: userTrust.isVerified() ? "user-verified": "warning", + }, this.props.onHeightChanged); // Decryption may have cause a change in size + return; + } + this.setState({ - verified: verified, - }, () => { - // Decryption may have caused a change in size - this.props.onHeightChanged(); - }); + verified: eventSenderTrust.isVerified() ? "verified" : "warning", + }, this.props.onHeightChanged); // Decryption may have caused a change in size }, _propsEqual: function(objA, objB) { @@ -473,8 +499,10 @@ export default createReactClass({ // event is encrypted, display padlock corresponding to whether or not it is verified if (ev.isEncrypted()) { - if (this.state.verified) { + if (this.state.verified === "verified") { return; // no icon for verified + } else if (this.state.verified === "user-verified") { + return (); } else { return (); } @@ -604,8 +632,9 @@ export default createReactClass({ mx_EventTile_last: this.props.last, mx_EventTile_contextual: this.props.contextual, mx_EventTile_actionBarFocused: this.state.actionBarFocused, - mx_EventTile_verified: !isBubbleMessage && this.state.verified === true, - mx_EventTile_unverified: !isBubbleMessage && this.state.verified === false, + mx_EventTile_verified: !isBubbleMessage && this.state.verified === "verified", + mx_EventTile_unverified: !isBubbleMessage && this.state.verified === "warning", + mx_EventTile_userVerified: !isBubbleMessage && this.state.verified === "user-verified", mx_EventTile_bad: isEncryptionFailure, mx_EventTile_emote: msgtype === 'm.emote', mx_EventTile_redacted: isRedacted, @@ -901,6 +930,12 @@ function E2ePadlockUnencrypted(props) { ); } +function E2ePadlockUserVerified(props) { + return ( + + ); +} + class E2ePadlock extends React.Component { static propTypes = { icon: PropTypes.string.isRequired, From 51fb3b494f8a87f28e2b20f9eafe0e96275de924 Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 20 Jan 2020 15:25:01 +0000 Subject: [PATCH 2/6] lint and i18n --- src/components/views/rooms/EventTile.js | 2 +- src/i18n/strings/en_EN.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 4aefe6929b..f2a77935bc 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -299,7 +299,7 @@ export default createReactClass({ const verified = await this.context.isEventSenderVerified(mxEvent); if (verified) { this.setState({ - verified: "verified" + verified: "verified", }, () => { // Decryption may have caused a change in size this.props.onHeightChanged(); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4af203177c..510c1be0c7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -906,6 +906,7 @@ "This message cannot be decrypted": "This message cannot be decrypted", "Encrypted by an unverified device": "Encrypted by an unverified device", "Unencrypted": "Unencrypted", + "Encrypted by a deleted device": "Encrypted by a deleted device", "Please select the destination room for this message": "Please select the destination room for this message", "Scroll to bottom of page": "Scroll to bottom of page", "Close preview": "Close preview", From 12c4e453870f4570aa2cb85631be8efdfc423e97 Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 20 Jan 2020 17:14:31 +0000 Subject: [PATCH 3/6] User verified but device deleted isn't a useful state --- res/css/views/rooms/_EventTile.scss | 14 +++++++------- res/themes/light/css/_light.scss | 2 +- src/components/views/rooms/EventTile.js | 14 ++++++-------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 81ba547ff0..e54255c4c4 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -367,7 +367,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { opacity: 1; } -.mx_EventTile_e2eIcon_userVerified { +.mx_EventTile_e2eIcon_unknown { background-image: url('$(res)/img/e2e/normal.svg'); opacity: 0.5; } @@ -421,7 +421,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line, .mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line, -.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line { +.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line { padding-left: 60px; } @@ -433,13 +433,13 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { border-left: $e2e-unverified-color 5px solid; } -.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line { - border-left: $e2e-userVerified-color 5px solid; +.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line { + border-left: $e2e-unknown-color 5px solid; } .mx_EventTile:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line, .mx_EventTile:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line, -.mx_EventTile:hover.mx_EventTile_userVerified.mx_EventTile_info .mx_EventTile_line { +.mx_EventTile:hover.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line { padding-left: 78px; } @@ -451,7 +451,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { // Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies) .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > a > .mx_MessageTimestamp, .mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp, -.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line > a > .mx_MessageTimestamp { +.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line > a > .mx_MessageTimestamp { left: 3px; width: auto; } @@ -459,7 +459,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { // Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies) .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > .mx_EventTile_e2eIcon, .mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > .mx_EventTile_e2eIcon, -.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line > .mx_EventTile_e2eIcon { +.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line > .mx_EventTile_e2eIcon { display: block; left: 41px; } diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 17b9a344ef..c868c81549 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -224,7 +224,7 @@ $copy-button-url: "$(res)/img/icon_copy_message.svg"; // e2e $e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color -$e2e-userVerified-color: #e8bf37; +$e2e-unknown-color: #e8bf37; $e2e-unverified-color: #e8bf37; $e2e-warning-color: #ba6363; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index f2a77935bc..037b080aa3 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -309,10 +309,8 @@ export default createReactClass({ const eventSenderTrust = await this.context.checkEventSenderTrust(mxEvent); if (!eventSenderTrust) { - // We cannot find the device. Instead, we have to verify the user. - const userTrust = await this.context.checkUserTrust(mxEvent.getSender()); this.setState({ - verified: userTrust.isVerified() ? "user-verified": "warning", + verified: "unknown", }, this.props.onHeightChanged); // Decryption may have cause a change in size return; } @@ -501,8 +499,8 @@ export default createReactClass({ if (ev.isEncrypted()) { if (this.state.verified === "verified") { return; // no icon for verified - } else if (this.state.verified === "user-verified") { - return (); + } else if (this.state.verified === "unknown") { + return (); } else { return (); } @@ -634,7 +632,7 @@ export default createReactClass({ mx_EventTile_actionBarFocused: this.state.actionBarFocused, mx_EventTile_verified: !isBubbleMessage && this.state.verified === "verified", mx_EventTile_unverified: !isBubbleMessage && this.state.verified === "warning", - mx_EventTile_userVerified: !isBubbleMessage && this.state.verified === "user-verified", + mx_EventTile_unknown: !isBubbleMessage && this.state.verified === "unknown", mx_EventTile_bad: isEncryptionFailure, mx_EventTile_emote: msgtype === 'm.emote', mx_EventTile_redacted: isRedacted, @@ -930,9 +928,9 @@ function E2ePadlockUnencrypted(props) { ); } -function E2ePadlockUserVerified(props) { +function E2ePadlockUnknown(props) { return ( - + ); } From befd4e1f5a0f893722858b70280d278c5aa05b6d Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 20 Jan 2020 17:25:08 +0000 Subject: [PATCH 4/6] shout more for unknown devices, but keep the tooltip --- res/css/views/rooms/_EventTile.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index e54255c4c4..d292c729dd 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -368,8 +368,8 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { } .mx_EventTile_e2eIcon_unknown { - background-image: url('$(res)/img/e2e/normal.svg'); - opacity: 0.5; + background-image: url('$(res)/img/e2e/warning.svg'); + opacity: 1; } .mx_EventTile_e2eIcon_unencrypted { From d34f1e52ad1e7d499018f96f6942501c7598b005 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 21 Jan 2020 10:08:53 +0000 Subject: [PATCH 5/6] constants for e2estates --- src/components/views/rooms/EventTile.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 037b080aa3..9c73daaa50 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -66,6 +66,12 @@ const stateEventTileTypes = { 'm.room.related_groups': 'messages.TextualEvent', }; +const E2ESTATE = { + VERIFIED: "verified", + WARNING: "warning", + UNKNOWN: "unknown", +}; + // Add all the Mjolnir stuff to the renderer for (const evType of ALL_RULE_TYPES) { stateEventTileTypes[evType] = 'messages.TextualEvent'; @@ -299,7 +305,7 @@ export default createReactClass({ const verified = await this.context.isEventSenderVerified(mxEvent); if (verified) { this.setState({ - verified: "verified", + verified: E2ESTATE.VERIFIED, }, () => { // Decryption may have caused a change in size this.props.onHeightChanged(); @@ -310,13 +316,13 @@ export default createReactClass({ const eventSenderTrust = await this.context.checkEventSenderTrust(mxEvent); if (!eventSenderTrust) { this.setState({ - verified: "unknown", + verified: E2ESTATE.UNKNOWN, }, this.props.onHeightChanged); // Decryption may have cause a change in size return; } this.setState({ - verified: eventSenderTrust.isVerified() ? "verified" : "warning", + verified: eventSenderTrust.isVerified() ? E2ESTATE.VERIFIED : E2ESTATE.WARNING, }, this.props.onHeightChanged); // Decryption may have caused a change in size }, @@ -497,9 +503,9 @@ export default createReactClass({ // event is encrypted, display padlock corresponding to whether or not it is verified if (ev.isEncrypted()) { - if (this.state.verified === "verified") { + if (this.state.verified === E2ESTATE.VERIFIED) { return; // no icon for verified - } else if (this.state.verified === "unknown") { + } else if (this.state.verified === E2ESTATE.UNKNOWN) { return (); } else { return (); @@ -630,9 +636,9 @@ export default createReactClass({ mx_EventTile_last: this.props.last, mx_EventTile_contextual: this.props.contextual, mx_EventTile_actionBarFocused: this.state.actionBarFocused, - mx_EventTile_verified: !isBubbleMessage && this.state.verified === "verified", - mx_EventTile_unverified: !isBubbleMessage && this.state.verified === "warning", - mx_EventTile_unknown: !isBubbleMessage && this.state.verified === "unknown", + mx_EventTile_verified: !isBubbleMessage && this.state.verified === E2ESTATE.VERIFIED, + mx_EventTile_unverified: !isBubbleMessage && this.state.verified === E2ESTATE.WARNING, + mx_EventTile_unknown: !isBubbleMessage && this.state.verified === E2ESTATE.UNKNOWN, mx_EventTile_bad: isEncryptionFailure, mx_EventTile_emote: msgtype === 'm.emote', mx_EventTile_redacted: isRedacted, From 2480f709b31f9f05270430bf73235c904f029b2c Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 21 Jan 2020 17:19:10 +0000 Subject: [PATCH 6/6] E2ESTATE -> E2E_STATE --- src/components/views/rooms/EventTile.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 9c73daaa50..bcd32d2c9c 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -66,7 +66,7 @@ const stateEventTileTypes = { 'm.room.related_groups': 'messages.TextualEvent', }; -const E2ESTATE = { +const E2E_STATE = { VERIFIED: "verified", WARNING: "warning", UNKNOWN: "unknown", @@ -305,7 +305,7 @@ export default createReactClass({ const verified = await this.context.isEventSenderVerified(mxEvent); if (verified) { this.setState({ - verified: E2ESTATE.VERIFIED, + verified: E2E_STATE.VERIFIED, }, () => { // Decryption may have caused a change in size this.props.onHeightChanged(); @@ -316,13 +316,13 @@ export default createReactClass({ const eventSenderTrust = await this.context.checkEventSenderTrust(mxEvent); if (!eventSenderTrust) { this.setState({ - verified: E2ESTATE.UNKNOWN, + verified: E2E_STATE.UNKNOWN, }, this.props.onHeightChanged); // Decryption may have cause a change in size return; } this.setState({ - verified: eventSenderTrust.isVerified() ? E2ESTATE.VERIFIED : E2ESTATE.WARNING, + verified: eventSenderTrust.isVerified() ? E2E_STATE.VERIFIED : E2E_STATE.WARNING, }, this.props.onHeightChanged); // Decryption may have caused a change in size }, @@ -503,9 +503,9 @@ export default createReactClass({ // event is encrypted, display padlock corresponding to whether or not it is verified if (ev.isEncrypted()) { - if (this.state.verified === E2ESTATE.VERIFIED) { + if (this.state.verified === E2E_STATE.VERIFIED) { return; // no icon for verified - } else if (this.state.verified === E2ESTATE.UNKNOWN) { + } else if (this.state.verified === E2E_STATE.UNKNOWN) { return (); } else { return (); @@ -636,9 +636,9 @@ export default createReactClass({ mx_EventTile_last: this.props.last, mx_EventTile_contextual: this.props.contextual, mx_EventTile_actionBarFocused: this.state.actionBarFocused, - mx_EventTile_verified: !isBubbleMessage && this.state.verified === E2ESTATE.VERIFIED, - mx_EventTile_unverified: !isBubbleMessage && this.state.verified === E2ESTATE.WARNING, - mx_EventTile_unknown: !isBubbleMessage && this.state.verified === E2ESTATE.UNKNOWN, + mx_EventTile_verified: !isBubbleMessage && this.state.verified === E2E_STATE.VERIFIED, + mx_EventTile_unverified: !isBubbleMessage && this.state.verified === E2E_STATE.WARNING, + mx_EventTile_unknown: !isBubbleMessage && this.state.verified === E2E_STATE.UNKNOWN, mx_EventTile_bad: isEncryptionFailure, mx_EventTile_emote: msgtype === 'm.emote', mx_EventTile_redacted: isRedacted,