diff --git a/README.md b/README.md index 3ff77e9..bb78883 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,7 @@ 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 -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. +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. 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). diff --git a/package.json b/package.json index 2918b53..b450128 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "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 deleted file mode 100644 index a4ca23b..0000000 --- a/src/matrix-device-verification.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - diff --git a/src/matrix-device-verification.js b/src/matrix-device-verification.js deleted file mode 100644 index 98a7b7d..0000000 --- a/src/matrix-device-verification.js +++ /dev/null @@ -1,234 +0,0 @@ -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 diff --git a/src/matrix-server-config.html b/src/matrix-server-config.html index fee1d90..97259cb 100644 --- a/src/matrix-server-config.html +++ b/src/matrix-server-config.html @@ -30,7 +30,6 @@ deviceLabel: { type: "text", required: false }, accessToken: { type: "password", required: true }, deviceId: { type: "text", required: false }, - secretStoragePassphrase: { type: "password", required: false }, url: { type: "text", required: true }, }, defaults: { @@ -87,14 +86,6 @@ You can either provide/generate an access token yourself or use the login button above to do it automatically. View the node docs to figure out how to generate an Access Token manually. If you generated a user with shared secret registration you will already have an access token you can place here. -
- - -
-
- You can either provide/generate an access token yourself or use the login button above to do it automatically. View the node docs to figure out how to generate an Access Token manually. If you generated a user with shared secret registration you will already have an access token you can place here. -
-
diff --git a/src/matrix-server-config.js b/src/matrix-server-config.js index 99b2c82..0403916 100644 --- a/src/matrix-server-config.js +++ b/src/matrix-server-config.js @@ -30,10 +30,9 @@ module.exports = function(RED) { this.userId = this.credentials.userId; this.deviceLabel = this.credentials.deviceLabel || null; this.deviceId = this.credentials.deviceId || null; - this.secretStoragePassphrase = this.credentials.secretStoragePassphrase || null; this.url = this.credentials.url; this.autoAcceptRoomInvites = n.autoAcceptRoomInvites; - this.e2ee = this.enableE2ee = n.enableE2ee || false; + this.e2ee = n.enableE2ee || false; this.globalAccess = n.global; this.initializedAt = new Date(); @@ -43,53 +42,6 @@ module.exports = function(RED) { return; } - let cryptoCallbacks = undefined; - if(node.enableE2ee && node.secretStoragePassphrase && false) { - // cryptoCallbacks = { - // getSecretStorageKey: async function({ keys }, name) { - // const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; - // for (const [keyName, keyInfo] of Object.entries(keys)) { - // const key = await deriveKey(node.secretStoragePassphrase, keyInfo.passphrase.salt, keyInfo.passphrase.iterations); - // // const key = Uint8Array.of(36, 47, 159, 193, 29, 188, 180, 86, 189, 180, 207, 101, 79, 255, 93, 159, 228, 43, 160, 158, 98, 209, 84, 196, 137, 122, 119, 118, 11, 131, 75, 87); - // const { mac } = await encryptAES(ZERO_STR, key, "", keyInfo.iv); - // if (keyInfo.mac.replace(/=+$/g, '') === mac.replace(/=+$/g, '')) { - // return [keyName, key]; - // } - // } - // return null; - // }, - // async getDehydrationKey() { - // return node.secretStoragePassphrase; - // }, - // async generateDehydrationKey() { - // return {key: node.secretStoragePassphrase}; - // } - // }; - - cryptoCallbacks = { - getSecretStorageKey: async ({ keys }) => { - const backupPassphrase = node.secretStoragePassphrase; - if (!backupPassphrase) { - node.WARN("Missing secret storage key"); - return null; - } - let keyId = await node.matrixClient.getDefaultSecretStorageKeyId(); - if (keyId && !keys[keyId]) { - keyId = undefined; - } - if (!keyId) { - keyId = keys[0][0]; - } - const backupInfo = await node.matrixClient.getKeyBackupVersion(); - const key = await node.matrixClient.keyBackupKeyFromPassword( - backupPassphrase, - backupInfo - ); - return [keyId, key]; - }, - } - } - let localStorageDir = storageDir + '/' + MatrixFolderNameFromUserId(this.userId), localStorage = new LocalStorage(localStorageDir), initialSetup = false; @@ -112,16 +64,6 @@ module.exports = function(RED) { node.log("Matrix server connection ready."); node.emit("connected"); if(!initialSetup) { - if(node.enableE2ee && node.secretStoragePassphrase && !await node.matrixClient.isCrossSigningReady() && false) { - // bootstrap cross-signing - await node.matrixClient.bootstrapCrossSigning({ - // maybe we can skip this? - authUploadDeviceSigningKeys: () => { - return true; - } - }); - } - // store Device ID internally let stored_device_id = getStoredDeviceId(localStorage), device_id = this.matrixClient.getDeviceId(); @@ -180,8 +122,7 @@ module.exports = function(RED) { cryptoStore: new LocalStorageCryptoStore(localStorage), userId: this.userId, deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined, - verificationMethods: ["m.sas.v1"], - cryptoCallbacks: cryptoCallbacks + // verificationMethods: ["m.sas.v1"] }); // set globally if configured to do so