e2ee.ts (12079B)
1 import { 2 DeviceId, 3 DeviceLists, 4 KeysBackupRequest, 5 KeysClaimRequest, 6 KeysQueryRequest, 7 KeysUploadRequest, 8 OlmMachine, 9 RequestType, 10 RoomId, 11 RoomMessageRequest, 12 SignatureUploadRequest, 13 ToDeviceRequest, 14 UserId 15 } from "@mtrnord/matrix-sdk-crypto-js"; 16 import { MatrixClient } from "./client"; 17 import { OwnUser } from "./ownUser"; 18 import { Room } from "./room"; 19 import { IRoomEvent } from "./api/events"; 20 import { HostnameMissingError, NotLogeedInError, OlmMachineNotSetupError, SDKError } from "./utils"; 21 22 export class MatrixE2EE { 23 private olmMachine?: OlmMachine; 24 private outgoingRequestsBeingProcessed = false; 25 private missingSessionsBeingRequested = false; 26 27 constructor(private client: MatrixClient, private user: OwnUser) { } 28 29 public async decryptRoomEvent(roomID: string, event: IRoomEvent<any>): Promise<any> { 30 return await this.olmMachine?.decryptRoomEvent(JSON.stringify(event), new RoomId(roomID)); 31 } 32 33 public async receiveSyncData( 34 to_device_events: string, 35 changed_devices: DeviceLists, 36 one_time_key_counts: Map<any, any>, 37 unused_fallback_keys?: Set<any> 38 ): Promise<any> { 39 return await this.olmMachine?.receiveSyncChanges( 40 to_device_events, 41 changed_devices, 42 one_time_key_counts, 43 unused_fallback_keys 44 ); 45 } 46 47 public async encryptRoomEvent(roomID: RoomId, type: string, content: string): Promise<SDKError | any> { 48 if (!this.client.isLoggedIn) { 49 return new NotLogeedInError(); 50 } 51 if (!this.olmMachine) { 52 return new OlmMachineNotSetupError(); 53 } 54 return await this.olmMachine?.encryptRoomEvent(roomID, type, content); 55 } 56 57 public async initOlmMachine(userID: UserId, deviceID: DeviceId, storePassphrase?: string): Promise<void> { 58 this.olmMachine = await OlmMachine.initialize(userID, deviceID, "cetirizine-crypto", storePassphrase); 59 console.warn("Init olm done") 60 } 61 62 public async updateTrackedUsers(users: any[]): Promise<void> { 63 await this.olmMachine?.updateTrackedUsers(users); 64 } 65 66 public async sendIdentifyAndOneTimeKeys(): Promise<SDKError | void> { 67 if (!this.client.isLoggedIn) { 68 return new NotLogeedInError(); 69 } 70 if (!this.user.hostname) { 71 return new HostnameMissingError(); 72 } 73 if (!this.olmMachine) { 74 return new OlmMachineNotSetupError(); 75 } 76 77 if (this.outgoingRequestsBeingProcessed) { 78 return; 79 } 80 this.outgoingRequestsBeingProcessed = true; 81 82 const outgoing_requests = await this.olmMachine.outgoingRequests(); 83 84 for (const request of outgoing_requests) { 85 await this.processRequest(request); 86 } 87 88 await this.getMissingSessions(); 89 this.outgoingRequestsBeingProcessed = false; 90 } 91 92 public async shareKeysForRoom(room: Room): Promise<SDKError | void> { 93 if (!this.client.isLoggedIn) { 94 return new NotLogeedInError(); 95 } 96 if (!this.user.hostname) { 97 return new HostnameMissingError(); 98 } 99 if (!this.olmMachine) { 100 return new OlmMachineNotSetupError(); 101 } 102 const encryptionSettings = room.getEncryptionSettings(); 103 if (encryptionSettings) { 104 const requests = await this.olmMachine.shareRoomKey(new RoomId(room.roomID), room.getJoinedMemberIDs().map(id => new UserId(id)), encryptionSettings); 105 for (const request of requests) { 106 await this.processRequest(request); 107 } 108 } 109 } 110 111 public async getMissingSessions(): Promise<SDKError | void> { 112 if (!this.client.isLoggedIn) { 113 return new NotLogeedInError(); 114 } 115 if (!this.user.hostname) { 116 return new HostnameMissingError(); 117 } 118 if (!this.olmMachine) { 119 return new OlmMachineNotSetupError(); 120 } 121 122 if (this.missingSessionsBeingRequested) { 123 return; 124 } 125 this.missingSessionsBeingRequested = true; 126 127 const encryptedRooms = [...this.client.getRooms()].filter(room => room.isEncrypted()); 128 const users = encryptedRooms.map(room => room.getJoinedMemberIDs().map(id => new UserId(id))).flat(); 129 const request = await this.olmMachine?.getMissingSessions(users); 130 if (request) { 131 await this.processRequest(request); 132 } 133 134 this.missingSessionsBeingRequested = false; 135 } 136 137 private async processRequest(request: any): Promise<SDKError | void> { 138 if (!this.client.isLoggedIn) { 139 return new NotLogeedInError(); 140 } 141 if (!this.user.hostname) { 142 return new HostnameMissingError(); 143 } 144 if (!this.olmMachine) { 145 return new OlmMachineNotSetupError(); 146 } 147 // Check which type the request is 148 if (request.type === RequestType.KeysUpload) { 149 // Send the key 150 const request_typed = request as KeysUploadRequest; 151 const response = await fetch( 152 `${this.user.hostname}/_matrix/client/v3/keys/upload`, 153 { 154 method: "POST", 155 headers: { 156 "Content-Type": "application/json", 157 "Authorization": `Bearer ${this.user.access_token}` 158 }, 159 body: request_typed.body 160 } 161 ) 162 if (!response.ok) { 163 if (response.status === 401) { 164 await this.client.logout(); 165 console.error(response); 166 } 167 console.error("Failed to upload keys", response); 168 return; 169 } 170 this.olmMachine.markRequestAsSent(request_typed.id!, request_typed.type, await response.text()); 171 } else if (request.type === RequestType.KeysQuery) { 172 const request_typed = request as KeysQueryRequest; 173 const response = await fetch( 174 `${this.user.hostname}/_matrix/client/v3/keys/query`, 175 { 176 method: "POST", 177 headers: { 178 "Content-Type": "application/json", 179 "Authorization": `Bearer ${this.user.access_token}` 180 }, 181 body: request_typed.body 182 } 183 ) 184 if (!response.ok) { 185 if (response.status === 401) { 186 await this.client.logout(); 187 console.error(response); 188 } 189 console.error("Failed to query keys", response); 190 return; 191 } 192 this.olmMachine.markRequestAsSent(request_typed.id!, request_typed.type, await response.text()); 193 } else if (request.type === RequestType.KeysClaim) { 194 const request_typed = request as KeysClaimRequest; 195 const response = await fetch( 196 `${this.user.hostname}/_matrix/client/v3/keys/claim`, 197 { 198 method: "POST", 199 headers: { 200 "Content-Type": "application/json", 201 "Authorization": `Bearer ${this.user.access_token}` 202 }, 203 body: request_typed.body 204 } 205 ) 206 if (!response.ok) { 207 if (response.status === 401) { 208 await this.client.logout(); 209 console.error(response); 210 } 211 console.error("Failed to claim keys", response); 212 return; 213 } 214 this.olmMachine.markRequestAsSent(request_typed.id!, request_typed.type, await response.text()); 215 } else if (request.type === RequestType.ToDevice) { 216 const request_typed = request as ToDeviceRequest; 217 const response = await fetch( 218 `${this.user.hostname}/_matrix/client/v3/sendToDevice/${request_typed.event_type}/${request_typed.txn_id}`, 219 { 220 method: "PUT", 221 headers: { 222 "Content-Type": "application/json", 223 "Authorization": `Bearer ${this.user.access_token}` 224 }, 225 body: request_typed.body 226 } 227 ) 228 if (!response.ok) { 229 if (response.status === 401) { 230 await this.client.logout(); 231 console.error(response); 232 } 233 console.error("Failed to send to device", response); 234 return; 235 } 236 this.olmMachine.markRequestAsSent(request_typed.id ?? request_typed.txn_id, request_typed.type, await response.text()); 237 } else if (request.type === RequestType.SignatureUpload) { 238 const request_typed = request as SignatureUploadRequest; 239 const response = await fetch( 240 `${this.user.hostname}/_matrix/client/v3/keys/signatures/upload`, 241 { 242 method: "POST", 243 headers: { 244 "Content-Type": "application/json", 245 "Authorization": `Bearer ${this.user.access_token}` 246 }, 247 body: request_typed.body 248 } 249 ) 250 if (!response.ok) { 251 if (response.status === 401) { 252 await this.client.logout(); 253 console.error(response); 254 } 255 console.error("Failed to upload signatures", response); 256 return; 257 } 258 this.olmMachine.markRequestAsSent(request_typed.id!, request_typed.type, await response.text()); 259 } else if (request.type === RequestType.RoomMessage) { 260 const request_typed = request as RoomMessageRequest; 261 const response = await fetch( 262 `${this.user.hostname}/_matrix/client/v3/rooms/${request_typed.room_id}/send/${request_typed.event_type}/${request_typed.txn_id}`, 263 { 264 method: "PUT", 265 headers: { 266 "Content-Type": "application/json", 267 "Authorization": `Bearer ${this.user.access_token}` 268 }, 269 body: request_typed.body 270 } 271 ) 272 if (!response.ok) { 273 if (response.status === 401) { 274 await this.client.logout(); 275 console.error(response); 276 } 277 console.error("Failed to send message", response); 278 return; 279 } 280 this.olmMachine.markRequestAsSent(request_typed.id!, request_typed.type, await response.text()); 281 } else if (request.type === RequestType.KeysBackup) { 282 const request_typed = request as KeysBackupRequest; 283 const response = await fetch( 284 `${this.user.hostname}/_matrix/client/v3/room_keys/keys`, 285 { 286 method: "PUT", 287 headers: { 288 "Content-Type": "application/json", 289 "Authorization": `Bearer ${this.user.access_token}` 290 }, 291 body: request_typed.body 292 } 293 ) 294 if (!response.ok) { 295 if (response.status === 401) { 296 await this.client.logout(); 297 console.error(response); 298 } 299 console.error("Failed to backup keys", response); 300 return; 301 } 302 this.olmMachine.markRequestAsSent(request_typed.id!, request_typed.type, await response.text()); 303 } 304 } 305 306 public logoutE2ee(): void { 307 this.missingSessionsBeingRequested = false; 308 this.outgoingRequestsBeingProcessed = false; 309 } 310 }