async def _find_olm_sessions(self, session: OutboundGroupSession, user_id: UserID, device_id: DeviceID, device: DeviceIdentity ) -> SessionEncryptResult: key = (user_id, device_id) if key in session.users_ignored or key in session.users_shared_with: return already_shared elif user_id == self.client.mxid and device_id == self.client.device_id: session.users_ignored.add(key) return already_shared if device.trust == TrustState.BLACKLISTED: self.log.debug(f"Not encrypting group session {session.id} for {device_id} " f"of {user_id}: device is blacklisted") session.users_ignored.add(key) return RoomKeyWithheldEventContent( room_id=session.room_id, algorithm=EncryptionAlgorithm.MEGOLM_V1, session_id=SessionID(session.id), sender_key=self.account.identity_key, code=RoomKeyWithheldCode.BLACKLISTED, reason="Device is blacklisted") elif not self.allow_unverified_devices and device.trust == TrustState.UNSET: self.log.debug(f"Not encrypting group session {session.id} for {device_id} " f"of {user_id}: device is not verified") session.users_ignored.add(key) return RoomKeyWithheldEventContent( room_id=session.room_id, algorithm=EncryptionAlgorithm.MEGOLM_V1, session_id=SessionID(session.id), sender_key=self.account.identity_key, code=RoomKeyWithheldCode.UNVERIFIED, reason="This device does not encrypt " "messages for unverified devices") device_session = await self.crypto_store.get_latest_session(device.identity_key) if not device_session: return key_missing session.users_shared_with.add(key) return device_session, device
async def _encrypt_megolm_event( self, room_id: RoomID, event_type: EventType, content: Any) -> EncryptedMegolmEventContent: self.log.debug(f"Encrypting event of type {event_type} for {room_id}") session = await self.crypto_store.get_outbound_group_session(room_id) if not session: raise EncryptionError("No group session created") ciphertext = session.encrypt( json.dumps({ "room_id": room_id, "type": event_type.serialize(), "content": content.serialize() if isinstance(content, Serializable) else content, })) try: relates_to = content.relates_to except AttributeError: try: relates_to = RelatesTo.deserialize(content["m.relates_to"]) except KeyError: relates_to = None await self.crypto_store.update_outbound_group_session(session) return EncryptedMegolmEventContent( sender_key=self.account.identity_key, device_id=self.client.device_id, ciphertext=ciphertext, session_id=SessionID(session.id), relates_to=relates_to, )
async def _new_outbound_group_session( self, room_id: RoomID) -> OutboundGroupSession: session = OutboundGroupSession(room_id) await self._create_group_session( self.account.identity_key, self.account.signing_key, room_id, SessionID(session.id), session.session_key, ) return session
async def update_session(self, key: IdentityKey, session: Session) -> None: try: assert self._olm_cache[key][SessionID(session.id)] == session except (KeyError, AssertionError) as e: self.log.warning( f"Cached olm session with ID {session.id} " f"isn't equal to the one being saved to the database ({e})") pickle = session.pickle(self.pickle_key) q = ( "UPDATE crypto_olm_session SET session=$1, last_encrypted=$2, last_decrypted=$3 " "WHERE session_id=$4 AND account_id=$5") await self.db.execute(q, pickle, session.last_encrypted, session.last_decrypted, session.id, self.account_id)
async def get_latest_session(self, key: IdentityKey) -> Session | None: q = ( "SELECT session_id, session, created_at, last_encrypted, last_decrypted " "FROM crypto_olm_session WHERE sender_key=$1 AND account_id=$2 " "ORDER BY last_decrypted DESC LIMIT 1") row = await self.db.fetchrow(q, key, self.account_id) if row is None: return None try: return self._olm_cache[key][row["session_id"]] except KeyError: sess = Session.from_pickle( row["session"], passphrase=self.pickle_key, creation_time=row["created_at"], last_encrypted=row["last_encrypted"], last_decrypted=row["last_decrypted"], ) self._olm_cache[key][SessionID(sess.id)] = sess return sess
async def add_session(self, key: IdentityKey, session: Session) -> None: if session.id in self._olm_cache[key]: self.log.warning( f"Cache already contains Olm session with ID {session.id}") self._olm_cache[key][SessionID(session.id)] = session pickle = session.pickle(self.pickle_key) q = """ INSERT INTO crypto_olm_session (session_id, sender_key, session, created_at, last_encrypted, last_decrypted, account_id) VALUES ($1, $2, $3, $4, $5, $6, $7) """ await self.db.execute( q, session.id, key, pickle, session.creation_time, session.last_encrypted, session.last_decrypted, self.account_id, )
async def get_sessions(self, key: IdentityKey) -> list[Session]: q = ( "SELECT session_id, session, created_at, last_encrypted, last_decrypted " "FROM crypto_olm_session WHERE sender_key=$1 AND account_id=$2 " "ORDER BY last_decrypted DESC") rows = await self.db.fetch(q, key, self.account_id) sessions = [] for row in rows: try: sess = self._olm_cache[key][row["session_id"]] except KeyError: sess = Session.from_pickle( row["session"], passphrase=self.pickle_key, creation_time=row["created_at"], last_encrypted=row["last_encrypted"], last_decrypted=row["last_decrypted"], ) self._olm_cache[key][SessionID(sess.id)] = sess sessions.append(sess) return sessions