Implement mac and done stage

This commit is contained in:
RMidhunSuresh 2023-03-04 22:30:53 +05:30
parent 78b5d69eb5
commit c9b462c803
No known key found for this signature in database
10 changed files with 285 additions and 23 deletions

View File

@ -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,
});
}
}

View File

@ -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 {

View File

@ -44,6 +44,7 @@ export interface IChannel {
waitForEvent(eventType: string): Promise<any>;
type: ChannelType;
id: string;
otherUserDeviceId: string;
sentMessages: Map<string, any>;
receivedMessages: Map<string, any>;
localMessages: Map<string, any>;
@ -74,7 +75,7 @@ export class ToDeviceChannel implements IChannel {
public readonly localMessages: Map<string, any> = new Map();
private readonly waitMap: Map<string, {resolve: any, promise: Promise<any>}> = 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});
});
}

View File

@ -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 {

View File

@ -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;
};
}

View File

@ -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) {

View File

@ -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";
}
}

View File

@ -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<void> = 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";
}

View File

@ -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<void> {
const mac: Record<string, string> = {};
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";
}
}

View File

@ -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<void> {
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<string, string>, verifier: KeyVerifier, log: ILogItem): Promise<void> {
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";
}
}