From d01733c6472c11d93623af82a2e5a63b80318898 Mon Sep 17 00:00:00 2001 From: Skylar Sadlier Date: Thu, 17 Mar 2022 19:38:29 -0600 Subject: [PATCH 1/5] closes #48 - Device verification can now be requested or received --- package.json | 1 + src/matrix-device-verification.html | 232 +++++++++++++++++++++++++++ src/matrix-device-verification.js | 234 ++++++++++++++++++++++++++++ 3 files changed, 467 insertions(+) create mode 100644 src/matrix-device-verification.html create mode 100644 src/matrix-device-verification.js diff --git a/package.json b/package.json index b8d9665..e55ba57 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "matrix-crypt-file": "src/matrix-crypt-file.js", "matrix-room-kick": "src/matrix-room-kick.js", "matrix-room-ban": "src/matrix-room-ban.js", + "matrix-device-verification": "src/matrix-device-verification.js", "matrix-synapse-users": "src/matrix-synapse-users.js", "matrix-synapse-register": "src/matrix-synapse-register.js", "matrix-synapse-create-edit-user": "src/matrix-synapse-create-edit-user.js", diff --git a/src/matrix-device-verification.html b/src/matrix-device-verification.html new file mode 100644 index 0000000..e7fe732 --- /dev/null +++ b/src/matrix-device-verification.html @@ -0,0 +1,232 @@ + + + + + diff --git a/src/matrix-device-verification.js b/src/matrix-device-verification.js new file mode 100644 index 0000000..98a7b7d --- /dev/null +++ b/src/matrix-device-verification.js @@ -0,0 +1,234 @@ +const {Phase} = require("matrix-js-sdk/lib/crypto/verification/request/VerificationRequest"); +const {CryptoEvent} = require("matrix-js-sdk/lib/crypto"); + +module.exports = function(RED) { + const verificationRequests = new Map(); + + function MatrixDeviceVerification(n) { + RED.nodes.createNode(this, n); + + var node = this; + + this.name = n.name; + this.server = RED.nodes.getNode(n.server); + this.mode = n.mode; + + if (!node.server) { + node.warn("No configuration node"); + return; + } + + if(!node.server.e2ee) { + node.error("End-to-end encryption needs to be enabled to use this."); + } + + node.status({ fill: "red", shape: "ring", text: "disconnected" }); + + node.server.on("disconnected", function(){ + node.status({ fill: "red", shape: "ring", text: "disconnected" }); + }); + + node.server.on("connected", function() { + node.status({ fill: "green", shape: "ring", text: "connected" }); + }); + + function getKeyByValue(object, value) { + return Object.keys(object).find(key => object[key] === value); + } + + switch(node.mode) { + default: + node.error("Node not configured with a mode"); + break; + + case 'request': + node.on('input', async function(msg){ + if(!msg.userId) { + node.error("msg.userId is required for start verification mode"); + } + + node.server.matrixClient.requestVerification(msg.userId, msg.devices || null) + .then(function(e) { + node.log("Successfully requested verification"); + let verifyRequestId = msg.userId + ':' + e.channel.deviceId; + verificationRequests.set(verifyRequestId, e); + node.send({ + verifyRequestId: verifyRequestId, // internally used to reference between nodes + verifyMethods: e.methods, + userId: msg.userId, + deviceIds: e.channel.devices, + selfVerification: e.isSelfVerification, + phase: getKeyByValue(Phase, e.phase) + }); + }) + .catch(function(e){ + node.warn("Error requesting device verification: " + e); + msg.error = e; + node.send([null, msg]); + }); + }); + break; + + case 'receive': + /** + * Fires when a key verification is requested. + * @event module:client~MatrixClient#"crypto.verification.request" + * @param {object} data + * @param {MatrixEvent} data.event the original verification request message + * @param {Array} data.methods the verification methods that can be used + * @param {Number} data.timeout the amount of milliseconds that should be waited + * before cancelling the request automatically. + * @param {Function} data.beginKeyVerification a function to call if a key + * verification should be performed. The function takes one argument: the + * name of the key verification method (taken from data.methods) to use. + * @param {Function} data.cancel a function to call if the key verification is + * rejected. + */ + node.server.matrixClient.on(CryptoEvent.VerificationRequest, async function(data){ + if(data.phase === Phase.Cancelled || data.phase === Phase.Done) { + return; + } + + if(data.requested || true) { + let verifyRequestId = data.targetDevice.userId + ':' + data.targetDevice.deviceId; + verificationRequests.set(verifyRequestId, data); + node.send({ + verifyRequestId: verifyRequestId, // internally used to reference between nodes + verifyMethods: data.methods, + userId: data.targetDevice.userId, + deviceId: data.targetDevice.deviceId, + selfVerification: data.isSelfVerification, + phase: getKeyByValue(Phase, data.phase) + }); + } + }); + + node.on('close', function(done) { + // clear verification requests + verificationRequests.clear(); + done(); + }); + break; + + case 'start': + node.on('input', async function(msg){ + if(!msg.verifyRequestId || !verificationRequests.has(msg.verifyRequestId)) { + // if(msg.userId && msg.deviceId) { + // node.server.beginKeyVerification("m.sas.v1", msg.userId, msg.deviceId); + // } + + node.error("invalid verification request (invalid msg.verifyRequestId): " + (msg.verifyRequestId || null)); + } + + var data = verificationRequests.get(msg.verifyRequestId); + if(msg.cancel) { + await data._verifier.cancel(); + verificationRequests.delete(msg.verifyRequestId); + } else { + try { + data.on('change', async function() { + var that = this; + if(this.phase === Phase.Started) { + let verifierCancel = function(){ + let verifyRequestId = that.targetDevice.userId + ':' + that.targetDevice.deviceId; + if(verificationRequests.has(verifyRequestId)) { + verificationRequests.delete(verifyRequestId); + } + }; + + data._verifier.on('cancel', function(e){ + node.warn("Device verification cancelled " + e); + verifierCancel(); + }); + + let show_sas = function(e) { + // e = { + // sas: { + // decimal: [ 8641, 3153, 2357 ], + // emoji: [ + // [Array], [Array], + // [Array], [Array], + // [Array], [Array], + // [Array] + // ] + // }, + // confirm: [AsyncFunction: confirm], + // cancel: [Function: cancel], + // mismatch: [Function: mismatch] + // } + msg.payload = e.sas; + msg.emojis = e.sas.emoji.map(function(emoji, i) { + return emoji[0]; + }); + msg.emojis_text = e.sas.emoji.map(function(emoji, i) { + return emoji[1]; + }); + node.send(msg); + }; + data._verifier.on('show_sas', show_sas); + data._verifier.verify() + .then(function(e){ + data._verifier.off('show_sas', show_sas); + data._verifier.done(); + }, function(e) { + verifierCancel(); + node.warn(e); + // @todo return over second output + }); + } + }); + + data.emit("change"); + await data.accept(); + } catch(e) { + console.log("ERROR", e); + } + } + }); + break; + + case 'cancel': + node.on('input', async function(msg){ + if(!msg.verifyRequestId || !verificationRequests.has(msg.verifyRequestId)) { + node.error("Invalid verification request: " + (msg.verifyRequestId || null)); + } + + var data = verificationRequests.get(msg.verifyRequestId); + if(data) { + data.cancel() + .then(function(e){ + node.send([msg, null]); + }) + .catch(function(e) { + msg.error = e; + node.send([null, msg]); + }); + } + }); + break; + + case 'accept': + node.on('input', async function(msg){ + if(!msg.verifyRequestId || !verificationRequests.has(msg.verifyRequestId)) { + node.error("Invalid verification request: " + (msg.verifyRequestId || null)); + } + + var data = verificationRequests.get(msg.verifyRequestId); + if(data._verifier && data._verifier.sasEvent) { + data._verifier.sasEvent.confirm() + .then(function(e){ + node.send([msg, null]); + }) + .catch(function(e) { + msg.error = e; + node.send([null, msg]); + }); + } else { + node.error("Verification must be started"); + } + }); + break; + } + } + RED.nodes.registerType("matrix-device-verification", MatrixDeviceVerification); +} \ No newline at end of file From 68cb5a026eedc02fdda3afba17200def0ffd1220 Mon Sep 17 00:00:00 2001 From: Skylar Sadlier Date: Thu, 17 Mar 2022 19:51:14 -0600 Subject: [PATCH 2/5] Update examples to include one for receiving and requesting device verification --- examples/README.md | 22 +++++ examples/request-device-verification.json | 92 ++++++++++++++++++ examples/request-device-verification.png | Bin 0 -> 17055 bytes .../start-accept-verification-from-user.json | 86 ++++++++++++++++ .../start-accept-verification-from-user.png | Bin 0 -> 15513 bytes 5 files changed, 200 insertions(+) create mode 100644 examples/request-device-verification.json create mode 100644 examples/request-device-verification.png create mode 100644 examples/start-accept-verification-from-user.json create mode 100644 examples/start-accept-verification-from-user.png diff --git a/examples/README.md b/examples/README.md index 5456690..569ad12 100644 --- a/examples/README.md +++ b/examples/README.md @@ -10,6 +10,8 @@ Build something cool with these nodes? Feel free to submit a pull request to sha - [Create User with Shared Secret Registration](#create-user-with-shared-secret-registration) - [Create/Edit Synapse User](#createedit-synapse-user) - [Use function node to run any command](#use-function-node-to-run-any-command) +- [Start and accept device verification from specific user](#start-and-accept-device-verification-from-specific-user) +- [Request device verification & immediately accept](#request-device-verification--immediately-accept) - [Respond to "ping" with "pong"](#respond-to-ping-with-pong) - [Respond to "html" with an HTML message](#respond-to-html-with-an-html-message) - [Respond to "image" with an uploaded image](#respond-to-image-with-an-uploaded-image) @@ -49,6 +51,26 @@ Allows an administrator to create or modify a user account with a specified `msg ![add-user-with-admin-user.png](add-user-with-admin-user.png) +### Request device verification & immediately accept + +[View JSON](request-device-verification.json) + +Edit the inject node to match the details of a user & device you would like to request verification from. +After the end user starts verification the bot automatically accepts the result (note: you should be validating the result and not just blindly accepting them, this is just an example) + +![add-user-with-admin-user.png](request-device-verification.png) + + +### Start and accept device verification from specific user + +[View JSON](start-accept-verification-from-user.json) + +Edit the switch node labeled "is from me" to match whatever user ID you would like to accept verification requests from. +After verification starts the bot automatically accepts the result (note: you should be validating the result and not just blindly accepting them, this is just an example) + +![add-user-with-admin-user.png](start-accept-verification-from-user.png) + + ### Use function node to run any command [View JSON](custom-redact-function-node.json) diff --git a/examples/request-device-verification.json b/examples/request-device-verification.json new file mode 100644 index 0000000..1029338 --- /dev/null +++ b/examples/request-device-verification.json @@ -0,0 +1,92 @@ +[ + { + "id": "9345e8c42e327dba", + "type": "matrix-device-verification", + "z": "f025a8b9fbd1b054", + "name": "", + "server": null, + "mode": "request", + "inputs": 1, + "outputs": 2, + "x": 480, + "y": 1660, + "wires": [ + [ + "b676082d56430aec" + ], + [] + ] + }, + { + "id": "b676082d56430aec", + "type": "matrix-device-verification", + "z": "f025a8b9fbd1b054", + "name": "", + "server": null, + "mode": "start", + "inputs": 1, + "outputs": 1, + "x": 740, + "y": 1660, + "wires": [ + [ + "23a0225f2f2615a3" + ] + ] + }, + { + "id": "23a0225f2f2615a3", + "type": "matrix-device-verification", + "z": "f025a8b9fbd1b054", + "name": "", + "server": null, + "mode": "accept", + "inputs": 1, + "outputs": 1, + "x": 970, + "y": 1660, + "wires": [ + [] + ] + }, + { + "id": "3eced60b58c999eb", + "type": "inject", + "z": "f025a8b9fbd1b054", + "name": "", + "props": [ + { + "p": "userId", + "v": "@bot:example.com", + "vt": "str" + }, + { + "p": "devices", + "v": "[\"ZRRJKASJDUK\"]", + "vt": "json" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 290, + "y": 1660, + "wires": [ + [ + "9345e8c42e327dba" + ] + ] + }, + { + "id": "f58ceba2a8819c09", + "type": "comment", + "z": "f025a8b9fbd1b054", + "name": "Request verification from a specific userId and device", + "info": "", + "x": 440, + "y": 1620, + "wires": [] + } +] \ No newline at end of file diff --git a/examples/request-device-verification.png b/examples/request-device-verification.png new file mode 100644 index 0000000000000000000000000000000000000000..0c1cc114c16f46e972499a46f5338c0169b0405d GIT binary patch literal 17055 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 Date: Thu, 17 Mar 2022 19:54:13 -0600 Subject: [PATCH 3/5] Update examples to move function example above verification ones. --- examples/README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/README.md b/examples/README.md index 569ad12..845ab78 100644 --- a/examples/README.md +++ b/examples/README.md @@ -51,6 +51,19 @@ Allows an administrator to create or modify a user account with a specified `msg ![add-user-with-admin-user.png](add-user-with-admin-user.png) +### Use function node to run any command + +[View JSON](custom-redact-function-node.json) + +If we do not have a node for something you want to do you can do this manually with a function node. We now have a node for removing events but this is still a good example. + +**Note:** You should make sure to catch any errors in your function node otherwise you could cause Node-RED to crash. + +To view what sort of functions you have access to check out the `client.ts` file from `matrix-js-sdk` [here](https://github.com/matrix-org/matrix-js-sdk/blob/master/src/client.ts). + +![custom-redact-function-node.png](custom-redact-function-node.png) + + ### Request device verification & immediately accept [View JSON](request-device-verification.json) @@ -70,19 +83,6 @@ After verification starts the bot automatically accepts the result (note: you sh ![add-user-with-admin-user.png](start-accept-verification-from-user.png) - -### Use function node to run any command - -[View JSON](custom-redact-function-node.json) - -If we do not have a node for something you want to do (such as redacting events/messages) you can do this manually with a function node. - -**Note:** You should make sure to catch any errors in your function node otherwise you could cause Node-RED to crash. - -To view what sort of functions you have access to check out the `client.ts` file from `matrix-js-sdk` [here](https://github.com/matrix-org/matrix-js-sdk/blob/master/src/client.ts). - -![custom-redact-function-node.png](custom-redact-function-node.png) - ### Respond to "ping" with "pong" [View JSON](respond-ping-pong.json) From 595fbca3df0697130c69f725f527183c37db6625 Mon Sep 17 00:00:00 2001 From: Skylar Sadlier Date: Thu, 17 Mar 2022 19:58:25 -0600 Subject: [PATCH 4/5] - Update main readme with new verification notes --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bb78883..3ff77e9 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,9 @@ You are not limited by just the nodes we have created. If you turn on global acc View an example [here](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#use-function-node-to-run-any-command) ### End-to-End Encryption Notes -Currently, this module has no way of getting encryption keys from other devices on the same account. Therefore it is recommended you use the bot exclusively with Node-RED after it's creation. Failure to do so will lead to your bot being unable to receive messages from e2ee rooms it joined from another client. Shared secret registration makes this super easy since it returns a token and device ID. +It is recommended you use the bot exclusively with Node-RED after it's creation if using e2ee. Failure to do so will lead to your bot being unable to receive messages from e2ee rooms it joined from another client. Shared secret registration makes this super easy since it returns a token and device ID. + +We now have a device verification node that will help in sharing keys (check the [examples](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme) for more info). This node is currently in beta and is still experimental. This module stores a folder in your Node-RED directory called `matrix-client-storage` and is it vital that you periodically back this up if you are using e2ee. This is where the client stores all the keys necessary to decrypt messages and if lost you will lose access to e2e rooms. If you move your client to another NR install make sure to migrate this folder as well (and do not let both the old and new client run at same time). From fef40f4ea998d3437a15ba9381568b2236ecc3f4 Mon Sep 17 00:00:00 2001 From: Skylar Sadlier Date: Thu, 17 Mar 2022 20:03:48 -0600 Subject: [PATCH 5/5] - Update matrix-device-verification node description with super basic info on how to use it --- src/matrix-device-verification.html | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/matrix-device-verification.html b/src/matrix-device-verification.html index e7fe732..a4ca23b 100644 --- a/src/matrix-device-verification.html +++ b/src/matrix-device-verification.html @@ -92,8 +92,16 @@