From b44142c0db177a6cf566ddabf6d8794c3b5ca1ce Mon Sep 17 00:00:00 2001 From: Skylar Sadlier Date: Mon, 15 Apr 2024 19:56:43 -0600 Subject: [PATCH] Verification process completes but fails to upload keys --- src/matrix-device-verification.js | 136 +++++++++++++++++++----------- src/matrix-server-config.js | 81 +++++++++++++++--- 2 files changed, 153 insertions(+), 64 deletions(-) diff --git a/src/matrix-device-verification.js b/src/matrix-device-verification.js index 51f57d9..530a5de 100644 --- a/src/matrix-device-verification.js +++ b/src/matrix-device-verification.js @@ -47,7 +47,7 @@ module.exports = function(RED) { node.error("msg.userId is required for start verification mode"); } - node.server.matrixClient.requestVerification(msg.userId, msg.devices || null) + node.server.matrixClient.requestDeviceVerification(msg.userId, msg.devices || undefined) .then(function(e) { node.log("Successfully requested verification", e); let verifyRequestId = msg.userId + ':' + e.channel.deviceId; @@ -84,7 +84,7 @@ module.exports = function(RED) { * @param {Function} data.cancel a function to call if the key verification is * rejected. */ - node.server.matrixClient.on(CryptoEvent.VerificationRequest, async function(data){ + node.server.matrixClient.on(CryptoEvent.VerificationRequestReceived, async function(data){ if(data.phase === Phase.Cancelled || data.phase === Phase.Done) { return; } @@ -118,13 +118,34 @@ module.exports = function(RED) { var data = verificationRequests.get(msg.verifyRequestId); if(msg.cancel) { - await data._verifier.cancel(); + await data.verifier.cancel(); verificationRequests.delete(msg.verifyRequestId); } else { try { data.on('change', async function() { + // VerificationPhase { + // /** Initial state: no event yet exchanged */ + // Unsent = 1, + // + // /** An `m.key.verification.request` event has been sent or received */ + // Requested = 2, + // + // /** An `m.key.verification.ready` event has been sent or received, indicating the verification request is accepted. */ + // Ready = 3, + // + // /** An `m.key.verification.start` event has been sent or received, choosing a verification method */ + // Started = 4, + // + // /** An `m.key.verification.cancel` event has been sent or received at any time before the `done` event, cancelling the verification request */ + // Cancelled = 5, + // + // /** An `m.key.verification.done` event has been **sent**, completing the verification request. */ + // Done = 6, + // } + console.log("[Verification Start] VERIFIER EVENT CHANGE", this.phase); var that = this; if(this.phase === Phase.Started) { + console.log("[Verification Start] VERIFIER EVENT PHASE STARTED"); let verifierCancel = function(){ let verifyRequestId = that.targetDevice.userId + ':' + that.targetDevice.deviceId; if(verificationRequests.has(verifyRequestId)) { @@ -132,51 +153,66 @@ module.exports = function(RED) { } }; - data._verifier.on('cancel', function(e){ + data.verifier.on('cancel', function(e){ node.warn("Device verification cancelled " + e); - console.log(e.value); + console.log(JSON.stringify(e.value)); verifierCancel(); }); + const sasEventPromise = new Promise(resolve => + data.verifier.once("show_sas", resolve) + ); + console.log("[Verification Start] Starting verification"); + data.verifier.verify() + .then(function() { + console.log("[Verification Start] verify() success"); + }).catch(function(e) { + console.log("[Verification Start] verify() error", e); + msg.error = e; + node.send([null, msg]); + }); + console.log("[Verification Start] WAITING FOR SHOW SAS EVENT"); + const sasEvent = await sasEventPromise; - 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 - }); + console.log("SHOW SAS", sasEvent); + // 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 = sasEvent.sas; + msg.emojis = sasEvent.sas.emoji.map(function(emoji, i) { + return emoji[0]; + }); + msg.emojis_text = sasEvent.sas.emoji.map(function(emoji, i) { + return emoji[1]; + }); + node.send(msg); + + // sasEvent.mismatch(); } }); - data.emit("change"); - await data.accept(); + console.log("[Verification Start] Starting verification"); + try { + console.log("[Verification Start] Accepting.."); + await data.accept(); + console.log(`[Verification] beginKeyVerification (methods=${data.methods[0]}, targetDevice=${data.targetDevice})`); + await data.beginKeyVerification( + data.methods[0], + data.targetDevice + ); + } catch(e) { + console.log("[Verification Start] VERIFICATION ERROR", e); + } } catch(e) { console.log("ERROR", e); } @@ -211,15 +247,15 @@ module.exports = function(RED) { } 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]); - }); + if(data.verifier && data.verifier.sasEvent) { + try { + await data.verifier.sasEvent.confirm(); + node.send([msg, null]); + } catch(e) { + + msg.error = e; + node.send([null, msg]); + } } else { node.error("Verification must be started"); } diff --git a/src/matrix-server-config.js b/src/matrix-server-config.js index 3a844c7..b126cec 100644 --- a/src/matrix-server-config.js +++ b/src/matrix-server-config.js @@ -111,6 +111,61 @@ module.exports = function(RED) { } else if(!this.url) { node.error("Matrix connection failed: missing server URL in configuration.", {}); } else { + /** + * Ensures secret storage and cross signing are ready for use. Does not + * support initial setup of secret storage. If the backup passphrase is not + * set, this is a no-op, else it is cleared once the operation is complete. + * + * @returns {Promise} + */ + async function bootstrapSSSS() { + if (!node.matrixClient) { + // client startup will do bootstrapping + return; + } + const password = "testphrase"; + if (!password) { + // We do not support setting up secret storage, so we need a passphrase + // to bootstrap. + return; + } + const backupInfo = await node.matrixClient.getKeyBackupVersion(); + await node.matrixClient.getCrypto().bootstrapSecretStorage({ + setupNewKeyBackup: false, + async getKeyBackupPassphrase() { + const key = await node.matrixClient.keyBackupKeyFromPassword( + password, + backupInfo + ); + return key; + }, + }); + await node.matrixClient.getCrypto().bootstrapCrossSigning({ + authUploadDeviceSigningKeys(makeRequest) { + console.log("authUploadDeviceSigningKeys"); + makeRequest({ + "type": "m.login.password", + "identifier": { + "type": "m.id.user", + "user": node.matrixClient.getUserId() + }, + "password": "examplepass", + "session": node.matrixClient.getSessionId() + }); + return Promise.resolve(); + }, + }); + await node.matrixClient.checkOwnCrossSigningTrust(); + if (backupInfo) { + await node.matrixClient.restoreKeyBackupWithSecretStorage(backupInfo); + } + // Clear passphrase once bootstrap was successful + // this.imAccount.setString("backupPassphrase", ""); + // this.imAccount.save(); + // this._encryptionError = ""; + // await this.updateEncryptionStatus(); + } + node.setConnected = async function(connected, cb) { if (node.connected !== connected) { node.connected = connected; @@ -122,21 +177,12 @@ module.exports = function(RED) { node.log("Matrix server connection ready."); node.emit("connected"); if(!initialSetup) { - if(node.e2ee && !await node.matrixClient.isCrossSigningReady()) { + console.log("INITIAL SETUP", await node.matrixClient.getCrypto().getCrossSigningStatus()); + if(node.e2ee && !await node.matrixClient.getCrypto().isCrossSigningReady()) { // bootstrap cross-signing - await node.matrixClient.bootstrapCrossSigning({ - // maybe we can skip this? - authUploadDeviceSigningKeys: async (func) => { - await func({}); - } - // authUploadDeviceSigningKeys: async (makeRequest) => { - // return await makeRequest({ - // type: 'm.login.token', - // token: node.credentials.accessToken, - // }); - // } - }); - await node.matrixClient.checkOwnCrossSigningTrust(); + await bootstrapSSSS(); + let crossSigningStatus = node.matrixClient.getCrypto().getCrossSigningStatus(); + console.log("crossSigningStatus", crossSigningStatus); } // store Device ID internally @@ -215,6 +261,11 @@ module.exports = function(RED) { cryptoCallbacks: { getCrossSigningKey, saveCrossSigningKeys }, }); + node.matrixClient.on("crypto.keyBackupStatus", function() { + console.log("crypto.keyBackupStatus"); + bootstrapSSSS(); + }); + node.debug(`hasLazyLoadMembersEnabled=${node.matrixClient.hasLazyLoadMembersEnabled()}`); // set globally if configured to do so @@ -457,6 +508,8 @@ module.exports = function(RED) { if(node.e2ee){ node.log("Initializing crypto..."); await node.matrixClient.initCrypto(); + node.log("Bootstrapping SSSS..."); + await bootstrapSSSS(); node.matrixClient.getCrypto().globalBlacklistUnverifiedDevices = false; // prevent errors from unverified devices } node.log("Connecting to Matrix server...");