mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2025-01-22 18:21:39 +01:00
Implement mac and done stage
This commit is contained in:
parent
78b5d69eb5
commit
c9b462c803
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
31
src/matrix/verification/SAS/mac.ts
Normal file
31
src/matrix/verification/SAS/mac.ts
Normal 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;
|
||||
};
|
||||
}
|
@ -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) {
|
||||
|
32
src/matrix/verification/SAS/stages/SendDoneStage.ts
Normal file
32
src/matrix/verification/SAS/stages/SendDoneStage.ts
Normal 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";
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
|
78
src/matrix/verification/SAS/stages/SendMacStage.ts
Normal file
78
src/matrix/verification/SAS/stages/SendMacStage.ts
Normal 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";
|
||||
}
|
||||
}
|
||||
|
95
src/matrix/verification/SAS/stages/VerifyMacStage.ts
Normal file
95
src/matrix/verification/SAS/stages/VerifyMacStage.ts
Normal 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";
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user