From c9b462c80323300c619fd3b2b967525c7817d609 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sat, 4 Mar 2023 22:30:53 +0530 Subject: [PATCH] Implement mac and done stage --- src/matrix/verification/CrossSigning.ts | 5 +- .../verification/SAS/SASVerification.ts | 25 ++--- .../verification/SAS/channel/Channel.ts | 7 +- src/matrix/verification/SAS/channel/types.ts | 2 + src/matrix/verification/SAS/mac.ts | 31 ++++++ .../SAS/stages/BaseSASVerificationStage.ts | 12 +++ .../verification/SAS/stages/SendDoneStage.ts | 32 +++++++ .../verification/SAS/stages/SendKeyStage.ts | 21 +++- .../verification/SAS/stages/SendMacStage.ts | 78 +++++++++++++++ .../verification/SAS/stages/VerifyMacStage.ts | 95 +++++++++++++++++++ 10 files changed, 285 insertions(+), 23 deletions(-) create mode 100644 src/matrix/verification/SAS/mac.ts create mode 100644 src/matrix/verification/SAS/stages/SendDoneStage.ts create mode 100644 src/matrix/verification/SAS/stages/SendMacStage.ts create mode 100644 src/matrix/verification/SAS/stages/VerifyMacStage.ts diff --git a/src/matrix/verification/CrossSigning.ts b/src/matrix/verification/CrossSigning.ts index 83b95052..b1312e90 100644 --- a/src/matrix/verification/CrossSigning.ts +++ b/src/matrix/verification/CrossSigning.ts @@ -139,7 +139,10 @@ export class CrossSigning { ourUser: { userId: this.ownUserId, deviceId: this.deviceId }, otherUserId: userId, log, - channel + channel, + e2eeAccount: this.e2eeAccount, + deviceTracker: this.deviceTracker, + hsApi: this.hsApi, }); } } diff --git a/src/matrix/verification/SAS/SASVerification.ts b/src/matrix/verification/SAS/SASVerification.ts index aa19a6e7..54dcb9ea 100644 --- a/src/matrix/verification/SAS/SASVerification.ts +++ b/src/matrix/verification/SAS/SASVerification.ts @@ -18,8 +18,11 @@ import type {ILogItem} from "../../../logging/types"; import type {Room} from "../../room/Room.js"; import type {Platform} from "../../../platform/web/Platform.js"; import type {BaseSASVerificationStage, UserData} from "./stages/BaseSASVerificationStage"; +import type {Account} from "../../e2ee/Account.js"; +import type {DeviceTracker} from "../../e2ee/DeviceTracker.js"; import type * as OlmNamespace from "@matrix-org/olm"; import {IChannel} from "./channel/Channel"; +import {HomeServerApi} from "../../net/HomeServerApi"; type Olm = typeof OlmNamespace; @@ -32,6 +35,9 @@ type Options = { otherUserId: string; channel: IChannel; log: ILogItem; + e2eeAccount: Account; + deviceTracker: DeviceTracker; + hsApi: HomeServerApi; } export class SASVerification { @@ -39,29 +45,14 @@ export class SASVerification { private olmSas: Olm.SAS; constructor(options: Options) { - const { room, ourUser, otherUserId, log, olmUtil, olm, channel } = options; + const { room, ourUser, otherUserId, log, olmUtil, olm, channel, e2eeAccount, deviceTracker, hsApi } = options; const olmSas = new olm.SAS(); this.olmSas = olmSas; // channel.send("m.key.verification.request", {}, log); try { - const options = { room, ourUser, otherUserId, log, olmSas, olmUtil, channel }; + const options = { room, ourUser, otherUserId, log, olmSas, olmUtil, channel, e2eeAccount, deviceTracker, hsApi }; let stage: BaseSASVerificationStage = new RequestVerificationStage(options); this.startStage = stage; - - // stage.setNextStage(new WaitForIncomingMessageStage("m.key.verification.ready", options)); - // stage = stage.nextStage; - - // stage.setNextStage(new WaitForIncomingMessageStage("m.key.verification.start", options)); - // stage = stage.nextStage; - - // stage.setNextStage(new AcceptVerificationStage(options)); - // stage = stage.nextStage; - - // stage.setNextStage(new WaitForIncomingMessageStage("m.key.verification.key", options)); - // stage = stage.nextStage; - - // stage.setNextStage(new SendKeyStage(options)); - // stage = stage.nextStage; console.log("startStage", this.startStage); } finally { diff --git a/src/matrix/verification/SAS/channel/Channel.ts b/src/matrix/verification/SAS/channel/Channel.ts index 3b390616..3612de16 100644 --- a/src/matrix/verification/SAS/channel/Channel.ts +++ b/src/matrix/verification/SAS/channel/Channel.ts @@ -44,6 +44,7 @@ export interface IChannel { waitForEvent(eventType: string): Promise; type: ChannelType; id: string; + otherUserDeviceId: string; sentMessages: Map; receivedMessages: Map; localMessages: Map; @@ -74,7 +75,7 @@ export class ToDeviceChannel implements IChannel { public readonly localMessages: Map = new Map(); private readonly waitMap: Map}> = new Map(); private readonly log: ILogItem; - private otherUserDeviceId: string; + public otherUserDeviceId: string; public startMessage: any; public id: string; private _initiatedByUs: boolean; @@ -99,7 +100,7 @@ export class ToDeviceChannel implements IChannel { if (eventType === VerificationEventTypes.Request) { // Handle this case specially await this.handleRequestEventSpecially(eventType, content, log); - this.sentMessages.set(eventType, content); + this.sentMessages.set(eventType, {content}); return; } Object.assign(content, { transaction_id: this.id }); @@ -112,7 +113,7 @@ export class ToDeviceChannel implements IChannel { } } await this.hsApi.sendToDevice(eventType, payload, this.id, { log }).response(); - this.sentMessages.set(eventType, content); + this.sentMessages.set(eventType, {content}); }); } diff --git a/src/matrix/verification/SAS/channel/types.ts b/src/matrix/verification/SAS/channel/types.ts index 68c5bb89..f459f228 100644 --- a/src/matrix/verification/SAS/channel/types.ts +++ b/src/matrix/verification/SAS/channel/types.ts @@ -5,6 +5,8 @@ export const enum VerificationEventTypes { Accept = "m.key.verification.accept", Key = "m.key.verification.key", Cancel = "m.key.verification.cancel", + Mac = "m.key.verification.mac", + Done = "m.key.verification.done", } export const enum CancelTypes { diff --git a/src/matrix/verification/SAS/mac.ts b/src/matrix/verification/SAS/mac.ts new file mode 100644 index 00000000..9a6eddda --- /dev/null +++ b/src/matrix/verification/SAS/mac.ts @@ -0,0 +1,31 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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. +*/ + +const macMethods = { + "hkdf-hmac-sha256": "calculate_mac", + "org.matrix.msc3783.hkdf-hmac-sha256": "calculate_mac_fixed_base64", + "hkdf-hmac-sha256.v2": "calculate_mac_fixed_base64", + "hmac-sha256": "calculate_mac_long_kdf", +} as const; + +export type MacMethod = keyof typeof macMethods; + +export function createCalculateMAC(olmSAS: Olm.SAS, method: MacMethod) { + return function (input: string, info: string): string { + const mac = olmSAS[macMethods[method]](input, info); + return mac; + }; +} diff --git a/src/matrix/verification/SAS/stages/BaseSASVerificationStage.ts b/src/matrix/verification/SAS/stages/BaseSASVerificationStage.ts index 31ed908f..0e37188e 100644 --- a/src/matrix/verification/SAS/stages/BaseSASVerificationStage.ts +++ b/src/matrix/verification/SAS/stages/BaseSASVerificationStage.ts @@ -16,8 +16,11 @@ limitations under the License. import type {ILogItem} from "../../../../lib.js"; import type {Room} from "../../../room/Room.js"; import type * as OlmNamespace from "@matrix-org/olm"; +import type {Account} from "../../../e2ee/Account.js"; +import type {DeviceTracker} from "../../../e2ee/DeviceTracker.js"; import {Disposables} from "../../../../utils/Disposables"; import {IChannel} from "../channel/Channel.js"; +import {HomeServerApi} from "../../../net/HomeServerApi.js"; type Olm = typeof OlmNamespace; @@ -34,6 +37,9 @@ export type Options = { olmSas: Olm.SAS; olmUtil: Olm.Utility; channel: IChannel; + e2eeAccount: Account; + deviceTracker: DeviceTracker; + hsApi: HomeServerApi; } export abstract class BaseSASVerificationStage extends Disposables { @@ -48,6 +54,9 @@ export abstract class BaseSASVerificationStage extends Disposables { protected _nextStage: BaseSASVerificationStage; protected channel: IChannel; protected options: Options; + protected e2eeAccount: Account; + protected deviceTracker: DeviceTracker; + protected hsApi: HomeServerApi; constructor(options: Options) { super(); @@ -59,6 +68,9 @@ export abstract class BaseSASVerificationStage extends Disposables { this.olmSAS = options.olmSas; this.olmUtil = options.olmUtil; this.channel = options.channel; + this.e2eeAccount = options.e2eeAccount; + this.deviceTracker = options.deviceTracker; + this.hsApi = options.hsApi; } setRequestEventId(id: string) { diff --git a/src/matrix/verification/SAS/stages/SendDoneStage.ts b/src/matrix/verification/SAS/stages/SendDoneStage.ts new file mode 100644 index 00000000..d19ab723 --- /dev/null +++ b/src/matrix/verification/SAS/stages/SendDoneStage.ts @@ -0,0 +1,32 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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 {BaseSASVerificationStage} from "./BaseSASVerificationStage"; +import {VerificationEventTypes} from "../channel/types"; + + +export class SendDoneStage extends BaseSASVerificationStage { + + async completeStage() { + await this.log.wrap("VerifyMacStage.completeStage", async (log) => { + await this.channel.send(VerificationEventTypes.Done, {}, log); + this.dispose(); + }); + } + + get type() { + return "m.key.verification.accept"; + } +} diff --git a/src/matrix/verification/SAS/stages/SendKeyStage.ts b/src/matrix/verification/SAS/stages/SendKeyStage.ts index 199ec5d6..c97e423a 100644 --- a/src/matrix/verification/SAS/stages/SendKeyStage.ts +++ b/src/matrix/verification/SAS/stages/SendKeyStage.ts @@ -16,7 +16,8 @@ limitations under the License. import {BaseSASVerificationStage} from "./BaseSASVerificationStage"; import {generateEmojiSas} from "../generator"; import {ILogItem} from "../../../../lib"; -import { VerificationEventTypes } from "../channel/types"; +import {VerificationEventTypes} from "../channel/types"; +import {SendMacStage} from "./SendMacStage"; // From element-web type KeyAgreement = "curve25519-hkdf-sha256" | "curve25519"; @@ -71,15 +72,24 @@ const calculateKeyAgreement = { } as const; export class SendKeyStage extends BaseSASVerificationStage { + private resolve: () => void; async completeStage() { await this.log.wrap("SendKeyStage.completeStage", async (log) => { + const emojiConfirmationPromise: Promise = new Promise(r => { + this.resolve = r; + }); this.olmSAS.set_their_key(this.theirKey); const ourSasKey = this.olmSAS.get_pubkey(); await this.sendKey(ourSasKey, log); const sasBytes = this.generateSASBytes(); const emoji = generateEmojiSas(Array.from(sasBytes)); console.log("emoji", emoji); + if (this.channel.initiatedByUs) { + await this.channel.waitForEvent(VerificationEventTypes.Key); + } + // await emojiConfirmationPromise; + this._nextStage = new SendMacStage(this.options); this.dispose(); }); } @@ -97,7 +107,7 @@ export class SendKeyStage extends BaseSASVerificationStage { } private generateSASBytes(): Uint8Array { - const keyAgreement = this.channel.sentMessages.get(VerificationEventTypes.Accept).key_agreement_protocol; + const keyAgreement = this.channel.sentMessages.get(VerificationEventTypes.Accept).content.key_agreement_protocol; const otherUserDeviceId = this.channel.startMessage.content.from_device; const sasBytes = calculateKeyAgreement[keyAgreement]({ our: { @@ -116,6 +126,13 @@ export class SendKeyStage extends BaseSASVerificationStage { return sasBytes; } + emojiMatch(match: boolean) { + if (!match) { + // cancel the verification + } + + } + get type() { return "m.key.verification.accept"; } diff --git a/src/matrix/verification/SAS/stages/SendMacStage.ts b/src/matrix/verification/SAS/stages/SendMacStage.ts new file mode 100644 index 00000000..4d8f492c --- /dev/null +++ b/src/matrix/verification/SAS/stages/SendMacStage.ts @@ -0,0 +1,78 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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 {BaseSASVerificationStage} from "./BaseSASVerificationStage"; +import {ILogItem} from "../../../../lib"; +import {VerificationEventTypes} from "../channel/types"; +import type * as OlmNamespace from "@matrix-org/olm"; +import {createCalculateMAC} from "../mac"; +import {VerifyMacStage} from "./VerifyMacStage"; +type Olm = typeof OlmNamespace; + +export class SendMacStage extends BaseSASVerificationStage { + private calculateMAC: (input: string, info: string) => string; + + async completeStage() { + await this.log.wrap("SendMacStage.completeStage", async (log) => { + let acceptMessage; + if (this.channel.initiatedByUs) { + acceptMessage = this.channel.receivedMessages.get(VerificationEventTypes.Accept).content; + } + else { + acceptMessage = this.channel.sentMessages.get(VerificationEventTypes.Accept).content; + } + const macMethod = acceptMessage.message_authentication_code; + this.calculateMAC = createCalculateMAC(this.olmSAS, macMethod); + await this.sendMAC(log); + this.dispose(); + }); + } + + private async sendMAC(log: ILogItem): Promise { + const mac: Record = {}; + const keyList: string[] = []; + const baseInfo = + "MATRIX_KEY_VERIFICATION_MAC" + + this.ourUser.userId + + this.ourUser.deviceId + + this.otherUserId + + this.channel.otherUserDeviceId + + this.channel.id; + + const deviceKeyId = `ed25519:${this.ourUser.deviceId}`; + const deviceKeys = this.e2eeAccount.getDeviceKeysToSignWithCrossSigning(); + mac[deviceKeyId] = this.calculateMAC(deviceKeys.keys[deviceKeyId], baseInfo + deviceKeyId); + keyList.push(deviceKeyId); + + const {masterKey: crossSigningKey} = await this.deviceTracker.getCrossSigningKeysForUser(this.ourUser.userId, this.hsApi, log); + console.log("masterKey", crossSigningKey); + if (crossSigningKey) { + const crossSigningKeyId = `ed25519:${crossSigningKey}`; + mac[crossSigningKeyId] = this.calculateMAC(crossSigningKey, baseInfo + crossSigningKeyId); + keyList.push(crossSigningKeyId); + } + + const keys = this.calculateMAC(keyList.sort().join(","), baseInfo + "KEY_IDS"); + console.log("result", mac, keys); + await this.channel.send(VerificationEventTypes.Mac, { mac, keys }, log); + await this.channel.waitForEvent(VerificationEventTypes.Mac); + this._nextStage = new VerifyMacStage(this.options); + } + + get type() { + return "m.key.verification.accept"; + } +} + diff --git a/src/matrix/verification/SAS/stages/VerifyMacStage.ts b/src/matrix/verification/SAS/stages/VerifyMacStage.ts new file mode 100644 index 00000000..634b8ccb --- /dev/null +++ b/src/matrix/verification/SAS/stages/VerifyMacStage.ts @@ -0,0 +1,95 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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 {BaseSASVerificationStage} from "./BaseSASVerificationStage"; +import {ILogItem} from "../../../../lib"; +import {VerificationEventTypes} from "../channel/types"; +import {createCalculateMAC} from "../mac"; +import type * as OlmNamespace from "@matrix-org/olm"; +import { SendDoneStage } from "./SendDoneStage"; +type Olm = typeof OlmNamespace; + +export type KeyVerifier = (keyId: string, device: any, keyInfo: string) => void; + +export class VerifyMacStage extends BaseSASVerificationStage { + private calculateMAC: (input: string, info: string) => string; + + async completeStage() { + await this.log.wrap("VerifyMacStage.completeStage", async (log) => { + let acceptMessage; + if (this.channel.initiatedByUs) { + acceptMessage = this.channel.receivedMessages.get(VerificationEventTypes.Accept).content; + } + else { + acceptMessage = this.channel.sentMessages.get(VerificationEventTypes.Accept).content; + } + const macMethod = acceptMessage.message_authentication_code; + this.calculateMAC = createCalculateMAC(this.olmSAS, macMethod); + await this.checkMAC(log); + await this.channel.waitForEvent(VerificationEventTypes.Done); + this._nextStage = new SendDoneStage(this.options); + this.dispose(); + }); + } + + private async checkMAC(log: ILogItem): Promise { + const {content} = this.channel.receivedMessages.get(VerificationEventTypes.Mac); + const baseInfo = + "MATRIX_KEY_VERIFICATION_MAC" + + this.otherUserId + + this.channel.otherUserDeviceId + + this.ourUser.userId + + this.ourUser.deviceId + + this.channel.id; + + if ( content.keys !== this.calculateMAC(Object.keys(content.mac).sort().join(","), baseInfo + "KEY_IDS")) { + // cancel when MAC does not match! + console.log("Keys MAC Verification failed"); + } + + await this.verifyKeys(content.mac, (keyId, key, keyInfo) => { + if (keyInfo !== this.calculateMAC(key, baseInfo + keyId)) { + // cancel when MAC does not match! + console.log("mac obj MAC Verification failed"); + } + }, log); + } + + protected async verifyKeys(keys: Record, verifier: KeyVerifier, log: ILogItem): Promise { + const userId = this.otherUserId; + for (const [keyId, keyInfo] of Object.entries(keys)) { + const deviceId = keyId.split(":", 2)[1]; + const device = await this.deviceTracker.deviceForId(userId, deviceId, this.hsApi, log); + if (device) { + verifier(keyId, device.ed25519Key, keyInfo); + // todo: mark device as verified here + } else { + // If we were not able to find the device, then deviceId is actually the master signing key! + const msk = deviceId; + const {masterKey} = await this.deviceTracker.getCrossSigningKeysForUser(userId, this.hsApi, log); + if (masterKey === msk) { + verifier(keyId, masterKey, keyInfo); + // todo: mark user as verified her + } else { + // logger.warn(`verification: Could not find device ${deviceId} to verify`); + } + } + } + } + + get type() { + return "m.key.verification.accept"; + } +}