def search_user_directory(self, term: str) -> List[User]: """ Search user directory for a given term, returning a list of users Args: term: term to be searched for Returns: user_list: list of users returned by server-side search """ try: response = self.api._send("POST", "/user_directory/search", {"search_term": term}) except MatrixRequestError as ex: if ex.code >= 500: log.error( "Ignoring Matrix error in `search_user_directory`", exc_info=ex, term=term, ) return list() else: raise ex try: return [ User(self.api, _user["user_id"], _user["display_name"]) for _user in response["results"] ] except KeyError: return list()
def _user_from_id(self, user_id: str, display_name: Optional[str] = None) -> Optional[User]: try: return User(self._client.api, user_id, display_name) except ValueError: log.error("Matrix server returned an invalid user_id.") return None
def get_joined_members(self) -> List[User]: """ Return a list of members of this room. """ response = self.client.api.get_room_members(self.room_id) for event in response['chunk']: if event['content']['membership'] == 'join': user_id = event["state_key"] if user_id not in self._members: self._mkmembers( User(self.client.api, user_id, event['content'].get('displayname'))) return list(self._members.values())
def create_new_users_for_address(signer=None, number_of_users=1): users = list() if signer is None: signer = make_signer() for i in range(number_of_users): user_id = f"@{signer.address_hex.lower()}:server{i}" signature_bytes = signer.sign(user_id.encode()) signature_hex = encode_hex(signature_bytes) user = User(api=None, user_id=user_id, displayname=signature_hex) users.append(user) return users
def get_joined_members(self, force_resync: bool = False) -> List[User]: """ Return a list of members of this room. """ if force_resync: response = self.client.api.get_room_members(self.room_id) for event in response["chunk"]: if event["content"]["membership"] == "join": user_id = event["state_key"] if user_id not in self._members: self._mkmembers( User(self.client.api, user_id, event["content"].get("displayname"))) return list(self._members.values())
def _handle_message(self, room: Optional[Room], message: MatrixMessage) -> List[SignedMessage]: """Handle a single Matrix message. The matrix message is expected to be a NDJSON, and each entry should be a valid JSON encoded Raiden message. If `room` is None this means we are processing a `to_device` message """ is_valid_type = (message["type"] == "m.room.message" and message["content"]["msgtype"] == MatrixMessageType.TEXT.value) if not is_valid_type: return [] sender_id = message["sender"] self._displayname_cache.warm_users([User(self._client.api, sender_id)]) # handles the "Could not get 'display_name' for user" case try: displayname = self._displayname_cache.userid_to_displayname[ sender_id] except KeyError: log.exception("Could not warm display cache", peer_user=sender_id) return [] peer_address = validate_user_id_signature(sender_id, displayname) if not peer_address: log.debug( "Message from invalid user displayName signature", peer_user=sender_id, room=room, ) return [] data = message["content"]["body"] if not isinstance(data, str): log.warning( "Received message body not a string", peer_user=sender_id, peer_address=to_checksum_address(peer_address), room=room, ) return [] messages = deserialize_messages(data=data, peer_address=peer_address, rate_limiter=self._rate_limiter) if not messages: return [] return messages
def search_user_directory(self, term: str) -> List[User]: """ Search user directory for a given term, returning a list of users Args: term: term to be searched for Returns: user_list: list of users returned by server-side search """ response = self.api._send("POST", "/user_directory/search", {"search_term": term}) try: return [ User(self.api, _user["user_id"], _user["display_name"]) for _user in response["results"] ] except KeyError: return []
def search_user_directory(self, term: str) -> List[User]: """ Search user directory for a given term, returning a list of users Args: term: term to be searched for Returns: user_list: list of users returned by server-side search """ response = self.api._send('POST', '/user_directory/search', {'search_term': term}) try: return [ User(self.api, _user['user_id'], _user['display_name']) for _user in response['results'] ] except KeyError: return []
def mock_get_user(klass, user: Union[User, str]) -> User: return User(None, USERID1)
def _validate_userid_signature(user: User) -> Optional[Address]: match = USERID_RE.match(user.user_id) if not match: return None return to_canonical_address(match.group(1)) ADDR1 = Address(b"\x11" * 20) ADDR2 = Address(b'""""""""""""""""""""') INVALID_USER_ID = "bla:bla" USER0_ID = "@0x0000000000000000000000000000000000000000:server1" USER1_S1_ID = "@0x1111111111111111111111111111111111111111:server1" USER1_S2_ID = "@0x1111111111111111111111111111111111111111:server2" USER2_S1_ID = "@0x2222222222222222222222222222222222222222:server1" USER2_S2_ID = "@0x2222222222222222222222222222222222222222:server2" USER1_S1 = User(api=None, user_id=USER1_S1_ID) USER1_S2 = User(api=None, user_id=USER1_S2_ID) USER2_S1 = User(api=None, user_id=USER2_S1_ID) USER2_S2 = User(api=None, user_id=USER2_S2_ID) @pytest.fixture def user_directory_content(): return [] @pytest.fixture def dummy_matrix_client(user_directory_content): return DummyMatrixClient(USER0_ID, user_directory_content)
def user(self): return User(self.cli.api, self.user_id)
try: client.login(username, password) except MatrixRequestError as e: print(e) if e.code == 403: print("Bad username or password.") sys.exit(4) else: print("Check your server details are correct.") sys.exit(2) except MissingSchema as e: print("Bad URL format.") print(e) sys.exit(3) user = User(client.api, client.user_id) if len(sys.argv) < 5: print("Current Display Name: %s" % user.get_display_name()) displayname = input("New Display Name: ") else: displayname = sys.argv[4] try: user.set_display_name(displayname) except MatrixRequestError as e: print(e) sys.exit(11)
class TestCryptoStore(object): # Initialise a store and test some init code device_id = 'AUIETSRN' user_id = '@user:matrix.org' room_id = '!test:example.com' room = Room(None, room_id) user = User(None, user_id, '') room._members[user_id] = user db_name = 'test.db' db_path = mkdtemp() store_conf = { 'db_name': db_name, 'db_path': db_path } store = CryptoStore( user_id, device_id=device_id, db_path=db_path, db_name=db_name) db_filepath = os.path.join(db_path, db_name) assert os.path.exists(db_filepath) store.close() store = CryptoStore( user_id, device_id=device_id, db_path=db_path, db_name=db_name) @pytest.fixture(autouse=True, scope='class') def cleanup(self): yield os.remove(self.db_filepath) @pytest.fixture() def account(self): account = self.store.get_olm_account() if account is None: account = olm.Account() self.store.save_olm_account(account) return account @pytest.fixture() def curve_key(self, account): return account.identity_keys['curve25519'] @pytest.fixture() def ed_key(self, account): return account.identity_keys['ed25519'] @pytest.fixture() def device(self): return OlmDevice(None, self.user_id, self.device_id, store_conf=self.store_conf) def test_olm_account_persistence(self): account = olm.Account() identity_keys = account.identity_keys self.store.remove_olm_account() # Try to load inexisting account saved_account = self.store.get_olm_account() assert saved_account is None # Try to load inexisting account without device_id self.store.device_id = None with pytest.raises(ValueError): self.store.get_olm_account() self.store.device_id = self.device_id # Save and load self.store.save_olm_account(account) saved_account = self.store.get_olm_account() assert saved_account.identity_keys == identity_keys # Save and load without device_id self.store.save_olm_account(account) self.store.device_id = None saved_account = self.store.get_olm_account() assert saved_account.identity_keys == identity_keys assert self.store.device_id == self.device_id # Replace the account, causing foreign keys to be deleted self.store.save_sync_token('test') self.store.replace_olm_account(account) assert self.store.get_sync_token() is None # Load the account from an OlmDevice device = OlmDevice(None, self.user_id, self.device_id, store_conf=self.store_conf) assert device.olm_account.identity_keys == account.identity_keys # Load the account from an OlmDevice, without device_id device = OlmDevice(None, self.user_id, store_conf=self.store_conf) assert device.device_id == self.device_id def test_olm_sessions_persistence(self, account, curve_key, device): session = olm.OutboundSession(account, curve_key, curve_key) sessions = defaultdict(list) self.store.load_olm_sessions(sessions) assert not sessions assert not self.store.get_olm_sessions(curve_key) self.store.save_olm_session(curve_key, session) self.store.load_olm_sessions(sessions) assert sessions[curve_key][0].id == session.id saved_sessions = self.store.get_olm_sessions(curve_key) assert saved_sessions[0].id == session.id sessions.clear() saved_sessions = self.store.get_olm_sessions(curve_key, sessions) assert sessions[curve_key][0].id == session.id # Replace the session when its internal state has changed pickle = session.pickle() session.encrypt('test') self.store.save_olm_session(curve_key, session) saved_sessions = self.store.get_olm_sessions(curve_key) assert saved_sessions[0].pickle != pickle # Load sessions dynamically assert not device.olm_sessions with pytest.raises(AttributeError): device._olm_decrypt(None, curve_key) assert device.olm_sessions[curve_key][0].id == session.id device.olm_sessions.clear() device.device_keys[self.user_id][self.device_id] = device device.olm_ensure_sessions({self.user_id: [self.device_id]}) assert device.olm_sessions[curve_key][0].id == session.id # Test cascade deletion self.store.remove_olm_account() assert not self.store.get_olm_sessions(curve_key) def test_megolm_inbound_persistence(self, curve_key, ed_key, device): out_session = olm.OutboundGroupSession() session = MegolmInboundSession(out_session.session_key, ed_key) session.forwarding_chain.append(curve_key) sessions = defaultdict(lambda: defaultdict(dict)) self.store.load_inbound_sessions(sessions) assert not sessions assert not self.store.get_inbound_session(self.room_id, curve_key, session.id) self.store.save_inbound_session(self.room_id, curve_key, session) self.store.load_inbound_sessions(sessions) assert sessions[self.room_id][curve_key][session.id].id == session.id saved_session = self.store.get_inbound_session(self.room_id, curve_key, session.id) assert saved_session.id == session.id assert saved_session.forwarding_chain == [curve_key] sessions = {} saved_session = self.store.get_inbound_session(self.room_id, curve_key, session.id, sessions) assert sessions[session.id].id == session.id assert not device.megolm_inbound_sessions created = device.megolm_add_inbound_session( self.room_id, curve_key, ed_key, session.id, out_session.session_key) assert not created assert device.megolm_inbound_sessions[self.room_id][curve_key][session.id].id == \ session.id device.megolm_inbound_sessions.clear() content = { 'sender_key': curve_key, 'session_id': session.id, 'algorithm': device._megolm_algorithm, 'device_id': '' } event = { 'sender': '', 'room_id': self.room_id, 'content': content } with pytest.raises(KeyError): device.megolm_decrypt_event(event) assert device.megolm_inbound_sessions[self.room_id][curve_key][session.id].id == \ session.id self.store.remove_olm_account() assert not self.store.get_inbound_session(self.room_id, curve_key, session.id) @pytest.mark.usefixtures('account') def test_megolm_outbound_persistence(self, device): session = MegolmOutboundSession(max_messages=2, max_age=100000) session.message_count = 1 session.add_device(self.device_id) sessions = {} self.store.load_outbound_sessions(sessions) assert not sessions assert not self.store.get_outbound_session(self.room_id) self.store.save_outbound_session(self.room_id, session) self.store.save_megolm_outbound_devices(self.room_id, {self.device_id}) self.store.load_outbound_sessions(sessions) assert sessions[self.room_id].id == session.id assert sessions[self.room_id].devices == session.devices assert sessions[self.room_id].creation_time == session.creation_time assert sessions[self.room_id].max_messages == session.max_messages assert sessions[self.room_id].message_count == session.message_count assert sessions[self.room_id].max_age == session.max_age saved_session = self.store.get_outbound_session(self.room_id) assert saved_session.id == session.id assert saved_session.devices == session.devices assert saved_session.creation_time == session.creation_time assert saved_session.max_messages == session.max_messages assert saved_session.message_count == session.message_count assert saved_session.max_age == session.max_age sessions.clear() saved_session = self.store.get_outbound_session(self.room_id, sessions) assert sessions[self.room_id].id == session.id self.store.remove_outbound_session(self.room_id) assert not self.store.get_outbound_session(self.room_id) self.store.save_outbound_session(self.room_id, session) saved_session = self.store.get_outbound_session(self.room_id) # Verify the saved devices have been erased with the session assert not saved_session.devices room = Room(None, self.room_id) with pytest.raises(AttributeError): device.megolm_build_encrypted_event(room, {}) assert device.megolm_outbound_sessions[self.room_id].id == session.id self.store.remove_olm_account() assert not self.store.get_outbound_session(self.room_id) @pytest.mark.usefixtures('account') def test_device_keys_persistence(self, device): user_devices = {self.user_id: [self.device_id]} device_keys = defaultdict(dict) device._verified = True self.store.load_device_keys(None, device_keys) assert not device_keys assert not self.store.get_device_keys(None, user_devices, device_keys) assert not device_keys device_keys_to_save = {self.user_id: {self.device_id: device}} self.store.save_device_keys(device_keys_to_save) self.store.load_device_keys(None, device_keys) assert device_keys[self.user_id][self.device_id].curve25519 == \ device.curve25519 assert device_keys[self.user_id][self.device_id].verified device_keys.clear() devices = self.store.get_device_keys(None, user_devices)[self.user_id] assert devices[self.device_id].curve25519 == device.curve25519 assert self.store.get_device_keys(None, user_devices, device_keys) assert device_keys[self.user_id][self.device_id].curve25519 == \ device.curve25519 assert device_keys[self.user_id][self.device_id].verified # Test device verification persistence device.verified = False device.ignored = True devices = self.store.get_device_keys(None, user_devices)[self.user_id] assert not devices[self.device_id].verified assert devices[self.device_id].ignored # Test [] wildcard devices = self.store.get_device_keys(None, {self.user_id: []})[self.user_id] assert devices[self.device_id].curve25519 == device.curve25519 device.device_list.tracked_user_ids = {self.user_id} device.device_list.get_room_device_keys(self.room) assert device_keys[self.user_id][self.device_id].curve25519 == \ device.curve25519 # Test multiples [] device_keys.clear() user_id = 'test' device_id = 'test' device_keys_to_save[user_id] = {device_id: device} self.store.save_device_keys(device_keys_to_save) user_devices[user_id] = [] user_devices[self.user_id] = [] device_keys = self.store.get_device_keys(None, user_devices) assert device_keys[self.user_id][self.device_id].curve25519 == device.curve25519 assert device_keys[user_id][device_id].curve25519 == device.curve25519 # Try to verify a device that has no keys device._ed25519 = None with pytest.raises(ValueError): device.verified = False self.store.remove_olm_account() assert not self.store.get_device_keys(None, user_devices) @pytest.mark.usefixtures('account') def test_tracked_users_persistence(self): tracked_user_ids = set() tracked_user_ids_to_save = {self.user_id} self.store.load_tracked_users(tracked_user_ids) assert not tracked_user_ids self.store.save_tracked_users(tracked_user_ids_to_save) self.store.load_tracked_users(tracked_user_ids) assert tracked_user_ids == tracked_user_ids_to_save self.store.remove_tracked_users({self.user_id}) tracked_user_ids.clear() self.store.load_tracked_users(tracked_user_ids) assert not tracked_user_ids @pytest.mark.usefixtures('account') def test_sync_token_persistence(self): sync_token = 'test' assert not self.store.get_sync_token() self.store.save_sync_token(sync_token) assert self.store.get_sync_token() == sync_token sync_token = 'new' self.store.save_sync_token(sync_token) assert self.store.get_sync_token() == sync_token @pytest.mark.usefixtures('account') def test_key_requests(self): session_id = 'test' session_ids = set() self.store.load_outgoing_key_requests(session_ids) assert not session_ids self.store.add_outgoing_key_request(session_id) self.store.load_outgoing_key_requests(session_ids) assert session_id in session_ids session_ids.clear() self.store.remove_outgoing_key_request(session_id) self.store.load_outgoing_key_requests(session_ids) assert not session_ids def test_load_all(self, account, curve_key, ed_key, device): curve_key = account.identity_keys['curve25519'] session = olm.OutboundSession(account, curve_key, curve_key) out_session = MegolmOutboundSession() out_session.add_device(self.device_id) in_session = MegolmInboundSession(out_session.session_key, ed_key) device_keys_to_save = {self.user_id: {self.device_id: device}} self.store.save_inbound_session(self.room_id, curve_key, in_session) self.store.save_olm_session(curve_key, session) self.store.save_outbound_session(self.room_id, out_session) self.store.save_megolm_outbound_devices(self.room_id, {self.device_id}) self.store.save_device_keys(device_keys_to_save) device = OlmDevice( None, self.user_id, self.device_id, store_conf=self.store_conf, load_all=True) assert session.id in {s.id for s in device.olm_sessions[curve_key]} saved_in_session = \ device.megolm_inbound_sessions[self.room_id][curve_key][in_session.id] assert saved_in_session.id == in_session.id saved_out_session = device.megolm_outbound_sessions[self.room_id] assert saved_out_session.id == out_session.id assert saved_out_session.devices == out_session.devices assert device.device_keys[self.user_id][self.device_id].curve25519 == \ device.curve25519
class TestOlmDevice: cli = MatrixClient(HOSTNAME) user_id = '@user:matrix.org' room_id = '!test:example.com' device_id = 'QBUAZIFURK' alice = '@alice:example.com' alice_device_id = 'JLAFKJWSCS' alice_curve_key = 'mmFRSHuJVq3aTudx3KB3w5ZvSFQhgEcy8d+m+vkEfUQ' alice_ed_key = '4VjV3OhFUxWFAcO5YOaQVmTIn29JdRmtNh9iAxoyhkc' alice_device = Device(cli.api, alice, alice_device_id, database=DummyStore(), curve25519_key=alice_curve_key, ed25519_key=alice_ed_key) room = cli._mkroom(room_id) room._members[alice] = User(cli.api, alice) upload_url = HOSTNAME + MATRIX_V2_API_PATH + '/keys/upload' claim_url = HOSTNAME + MATRIX_V2_API_PATH + '/keys/claim' to_device_url = HOSTNAME + MATRIX_V2_API_PATH + '/sendToDevice/m.room.encrypted/1' @pytest.fixture() def device(self): device = OlmDevice(self.cli.api, self.user_id, self.device_id) # allow to_device api call to work well with responses device.api._make_txn_id = lambda: 1 return device @pytest.fixture() def signing_key(self, device): return device.olm_account.identity_keys['ed25519'] @pytest.fixture() def olm_session_with_alice(self, device): session = olm.OutboundSession(device.olm_account, self.alice_curve_key, self.alice_curve_key) device.device_keys[self.alice][ self.alice_device_id] = self.alice_device device.olm_sessions[self.alice_curve_key] = [session] @pytest.fixture() def alice_olm_device(self, device): """Establish an Olm session from Alice to us, and return Alice's Olm device.""" alice_device = OlmDevice(device.api, self.alice, self.alice_device_id) alice_device.device_keys[self.user_id][self.device_id] = device device.device_keys[self.alice][self.alice_device_id] = alice_device device.olm_account.generate_one_time_keys(1) otk = next( iter(device.olm_account.one_time_keys['curve25519'].values())) device.olm_account.mark_keys_as_published() sender_key = device.curve25519 session = olm.OutboundSession(alice_device.olm_account, sender_key, otk) alice_device.olm_sessions[sender_key] = [session] return alice_device def test_sign_json(self, device): example_payload = { "name": "example.org", "unsigned": { "age_ts": 922834800000 } } saved_payload = deepcopy(example_payload) signed_payload = device.sign_json(example_payload) signature = signed_payload.pop('signatures') # We should not have modified the payload besides the signatures key assert example_payload == saved_payload key_id = 'ed25519:' + device.device_id assert signature[self.user_id][key_id] def test_verify_json(self, device): example_payload = { "test": "test", "unsigned": { "age_ts": 922834800000 }, "signatures": { "@user:matrix.org": { "ed25519:QBUAZIFURK": ("WI7TgwqTp4YVn1dFWmDu7xrJvEikEzAbmoqyM5JY5t0P" "6fVaiMFAirmwb13GzIyYDLR+nQfoksNBcrp7xSaMCA") } } } saved_payload = deepcopy(example_payload) signing_key = "WQF5z9b4DV1DANI5HUMJfhTIDvJs1jkoGTLY6AQdjF0" assert device.verify_json(example_payload, signing_key, self.user_id, device.device_id) # We should not have modified the payload assert example_payload == saved_payload # Try to verify an object that has been tampered with example_payload['test'] = 'test1' assert not device.verify_json(example_payload, signing_key, self.user_id, device.device_id) # Try to verify invalid payloads example_payload['signatures'].pop(self.user_id) assert not device.verify_json(example_payload, signing_key, self.user_id, device.device_id) example_payload.pop('signatures') assert not device.verify_json(example_payload, signing_key, self.user_id, device.device_id) def test_sign_verify(self, device, signing_key): example_payload = { "name": "example.org", } signed_payload = device.sign_json(example_payload) assert device.verify_json(signed_payload, signing_key, self.user_id, device.device_id) @responses.activate def test_upload_identity_keys(self, device, signing_key): device.one_time_keys_manager.server_counts = {} resp = deepcopy(example_key_upload_response) responses.add(responses.POST, self.upload_url, json=resp) assert device.upload_identity_keys() is None assert device.one_time_keys_manager.server_counts == \ resp['one_time_key_counts'] req_device_keys = json.loads( responses.calls[0].request.body)['device_keys'] assert req_device_keys['user_id'] == self.user_id assert req_device_keys['device_id'] == self.device_id assert req_device_keys['algorithms'] == device._algorithms assert 'keys' in req_device_keys assert 'signatures' in req_device_keys assert device.verify_json(req_device_keys, signing_key, self.user_id, self.device_id) @pytest.mark.parametrize('proportion', [-1, 2]) def test_upload_identity_keys_invalid(self, proportion): with pytest.raises(ValueError): OlmDevice(self.cli.api, self.user_id, self.device_id, signed_keys_proportion=proportion) @responses.activate @pytest.mark.parametrize('proportion', [0, 1, 0.5, 0.33]) def test_upload_one_time_keys(self, proportion): resp = deepcopy(example_key_upload_response) counts = resp['one_time_key_counts'] counts['curve25519'] = counts['signed_curve25519'] = 10 responses.add(responses.POST, self.upload_url, json=resp) device = OlmDevice(self.cli.api, self.user_id, self.device_id, signed_keys_proportion=proportion) assert not device.one_time_keys_manager.server_counts max_keys = device.olm_account.max_one_time_keys // 2 signed_keys_to_upload = \ max(round(max_keys * proportion) - counts['signed_curve25519'], 0) unsigned_keys_to_upload = \ max(round(max_keys * (1 - proportion)) - counts['curve25519'], 0) expected_return = {} if signed_keys_to_upload: expected_return['signed_curve25519'] = signed_keys_to_upload if unsigned_keys_to_upload: expected_return['curve25519'] = unsigned_keys_to_upload assert device.upload_one_time_keys() == expected_return assert len(responses.calls) == 2 assert device.one_time_keys_manager.server_counts == resp[ 'one_time_key_counts'] req_otk = json.loads(responses.calls[1].request.body)['one_time_keys'] assert len(req_otk) == unsigned_keys_to_upload + signed_keys_to_upload assert len([key for key in req_otk if not key.startswith('signed')]) == \ unsigned_keys_to_upload assert len([key for key in req_otk if key.startswith('signed')]) == \ signed_keys_to_upload for k in req_otk: if k == 'signed_curve25519': device.verify_json(req_otk[k], device.signing_key, device.user_id, device.device_id) @responses.activate def test_upload_one_time_keys_enough(self, device): device.one_time_keys_manager.server_counts = {} limit = device.olm_account.max_one_time_keys // 2 resp = {'one_time_key_counts': {'signed_curve25519': limit}} responses.add(responses.POST, self.upload_url, json=resp) assert not device.upload_one_time_keys() @responses.activate def test_upload_one_time_keys_force_update(self, device): device.one_time_keys_manager.server_counts = {'curve25519': 10} resp = deepcopy(example_key_upload_response) responses.add(responses.POST, self.upload_url, json=resp) device.upload_one_time_keys() assert len(responses.calls) == 1 device.upload_one_time_keys(force_update=True) assert len(responses.calls) == 3 @responses.activate @pytest.mark.parametrize('count,should_upload', [(0, True), (25, False), (4, True)]) def test_update_one_time_key_counts(self, device, count, should_upload): responses.add(responses.POST, self.upload_url, json={'one_time_key_counts': {}}) device.one_time_keys_manager.target_counts['signed_curve25519'] = 50 count_dict = {} if count: count_dict['signed_curve25519'] = count device.update_one_time_key_counts(count_dict) if should_upload: if count: req_otk = json.loads( responses.calls[0].request.body)['one_time_keys'] assert len(responses.calls) == 1 else: req_otk = json.loads( responses.calls[1].request.body)['one_time_keys'] assert len(responses.calls) == 2 assert len(req_otk) == 50 - count else: assert not len(responses.calls) @pytest.mark.parametrize('threshold', [-1, 2]) def test_invalid_keys_threshold(self, threshold): with pytest.raises(ValueError): OlmDevice(self.cli.api, self.user_id, self.device_id, keys_threshold=threshold) @responses.activate def test_olm_start_sessions(self, device): responses.add(responses.POST, self.claim_url, json=example_claim_keys_response) user_devices = {self.alice: {self.alice_device_id}} # We don't have alice's keys device.olm_start_sessions(user_devices) assert not device.olm_sessions[self.alice_curve_key] # Cover logging part olm_device.logger.setLevel(logging.WARNING) # Now should be good device.device_keys[self.alice][ self.alice_device_id] = self.alice_device device.olm_start_sessions(user_devices) assert device.olm_sessions[self.alice_curve_key] # With failures and wrong signature device.olm_sessions.clear() payload = deepcopy(example_claim_keys_response) payload['failures'] = {'dummy': 1} key = payload['one_time_keys'][self.alice][self.alice_device_id] key['signed_curve25519:AAAAAQ']['test'] = 1 responses.replace(responses.POST, self.claim_url, json=payload) device.olm_start_sessions(user_devices) assert not device.olm_sessions[self.alice_curve_key] # Missing requested user and devices user_devices[self.alice].add('test') user_devices['test'] = 'test' device.olm_start_sessions(user_devices) @responses.activate def test_olm_build_encrypted_event(self, device): event_content = {'dummy': 'example'} # We don't have Alice's keys with pytest.raises(RuntimeError): device.olm_build_encrypted_event('m.text', event_content, self.alice, self.alice_device_id) # We don't have a session with Alice device.device_keys[self.alice][ self.alice_device_id] = self.alice_device with pytest.raises(RuntimeError): device.olm_build_encrypted_event('m.text', event_content, self.alice, self.alice_device_id) responses.add(responses.POST, self.claim_url, json=example_claim_keys_response) user_devices = {self.alice: {self.alice_device_id}} device.olm_start_sessions(user_devices) assert device.olm_build_encrypted_event('m.text', event_content, self.alice, self.alice_device_id) def test_olm_decrypt(self, device): # Since this method doesn't care about high-level event formatting, we will # generate things at low level our_account = device.olm_account # Alice needs to start a session with us alice = olm.Account() sender_key = alice.identity_keys['curve25519'] our_account.generate_one_time_keys(1) otk = next(iter(our_account.one_time_keys['curve25519'].values())) device.olm_account.mark_keys_as_published() session = olm.OutboundSession(alice, our_account.identity_keys['curve25519'], otk) plaintext = {"test": "test"} message = session.encrypt(json.dumps(plaintext)) assert device._olm_decrypt(message, sender_key) == plaintext # New pre-key message, but the session exists this time message = session.encrypt(json.dumps(plaintext)) assert device._olm_decrypt(message, sender_key) == plaintext # Try to decrypt the same message twice with pytest.raises(RuntimeError): device._olm_decrypt(message, sender_key) # Answer Alice in order to have a type 1 message message = device.olm_sessions[sender_key][0].encrypt( json.dumps(plaintext)) session.decrypt(message) message = session.encrypt(json.dumps(plaintext)) assert device._olm_decrypt(message, sender_key) == plaintext # Try to decrypt the same message type 1 twice with pytest.raises(RuntimeError): device._olm_decrypt(message, sender_key) # Try to decrypt a message from a session that reused a one-time key otk_reused_session = olm.OutboundSession( alice, our_account.identity_keys['curve25519'], otk) message = otk_reused_session.encrypt(json.dumps(plaintext)) with pytest.raises(RuntimeError): device._olm_decrypt(message, sender_key) # Try to decrypt an invalid type 0 message our_account.generate_one_time_keys(1) otk = next(iter(our_account.one_time_keys['curve25519'].values())) wrong_session = olm.OutboundSession(alice, sender_key, otk) message = wrong_session.encrypt(json.dumps(plaintext)) with pytest.raises(RuntimeError): device._olm_decrypt(message, sender_key) # Try to decrypt a type 1 message for which we have no sessions message = session.encrypt(json.dumps(plaintext)) device.olm_sessions.clear() with pytest.raises(RuntimeError): device._olm_decrypt(message, sender_key) def test_olm_decrypt_event(self, device, alice_olm_device): encrypted_event = alice_olm_device.olm_build_encrypted_event( 'example_type', {'content': 'test'}, self.user_id, self.device_id) # Now we can test device.olm_decrypt_event(encrypted_event, self.alice) # Device verification alice_olm_device.verified = True encrypted_event = alice_olm_device.olm_build_encrypted_event( 'example_type', {'content': 'test'}, self.user_id, self.device_id) device.olm_decrypt_event(encrypted_event, self.alice) # The signing_key is wrong encrypted_event = alice_olm_device.olm_build_encrypted_event( 'example_type', {'content': 'test'}, self.user_id, self.device_id) device.device_keys[self.alice][self.alice_device_id]._ed25519 = 'wrong' with pytest.raises(RuntimeError): device.olm_decrypt_event(encrypted_event, self.alice) # We do not have the keys encrypted_event = alice_olm_device.olm_build_encrypted_event( 'example_type', {'content': 'test'}, self.user_id, self.device_id) device.device_keys[self.alice].clear() device.olm_decrypt_event(encrypted_event, self.alice) device.device_keys[self.alice][self.alice_device_id] = alice_olm_device alice_olm_device.verified = False # Type 1 Olm payload alice_olm_device.olm_decrypt_event( device.olm_build_encrypted_event('example_type', {'content': 'test'}, self.alice, self.alice_device_id), self.user_id) encrypted_event = alice_olm_device.olm_build_encrypted_event( 'example_type', {'content': 'test'}, self.user_id, self.device_id) device.olm_decrypt_event(encrypted_event, self.alice) encrypted_event = alice_olm_device.olm_build_encrypted_event( 'example_type', {'content': 'test'}, self.user_id, self.device_id) with pytest.raises(RuntimeError): device.olm_decrypt_event(encrypted_event, 'wrong') wrong_event = deepcopy(encrypted_event) wrong_event['algorithm'] = 'wrong' with pytest.raises(RuntimeError): device.olm_decrypt_event(wrong_event, self.alice) wrong_event = deepcopy(encrypted_event) wrong_event['ciphertext'] = {} with pytest.raises(RuntimeError): device.olm_decrypt_event(wrong_event, self.alice) encrypted_event = alice_olm_device.olm_build_encrypted_event( 'example_type', {'content': 'test'}, self.user_id, self.device_id) device.user_id = 'wrong' with pytest.raises(RuntimeError): device.olm_decrypt_event(encrypted_event, self.alice) device.user_id = self.user_id encrypted_event = alice_olm_device.olm_build_encrypted_event( 'example_type', {'content': 'test'}, self.user_id, self.device_id) device._ed25519 = 'wrong' with pytest.raises(RuntimeError): device.olm_decrypt_event(encrypted_event, self.alice) @responses.activate def test_olm_ensure_sessions(self, device): responses.add(responses.POST, self.claim_url, json=example_claim_keys_response) device.device_keys[self.alice][ self.alice_device_id] = self.alice_device user_devices = {self.alice: [self.alice_device_id]} device.olm_ensure_sessions(user_devices) assert device.olm_sessions[self.alice_curve_key] assert len(responses.calls) == 1 device.olm_ensure_sessions(user_devices) assert len(responses.calls) == 1 @responses.activate def test_megolm_share_session(self, device): responses.add(responses.POST, self.claim_url, json=example_claim_keys_response) responses.add(responses.PUT, self.to_device_url, json={}) device.device_keys[self.alice][ self.alice_device_id] = self.alice_device device.device_keys['dummy']['dummy'] = \ Device(self.cli.api, 'dummy', 'dummy', curve25519_key='a', ed25519_key='a') user_devices = {self.alice: [self.alice_device_id], 'dummy': ['dummy']} session = MegolmOutboundSession() # Sharing with Alice should succeed, but dummy will fail device.megolm_share_session(self.room_id, user_devices, session) assert session.devices == {self.alice_device_id, 'dummy'} req = json.loads(responses.calls[1].request.body)['messages'] assert self.alice in req assert 'dummy' not in req @responses.activate @pytest.mark.usefixtures('olm_session_with_alice') def test_megolm_start_session(self, device): responses.add(responses.PUT, self.to_device_url, json={}) device.device_list.tracked_user_ids.add(self.alice) user_devices = {self.alice: [self.alice_device_id]} device.megolm_start_session(self.room, user_devices) session = device.megolm_outbound_sessions[self.room_id] assert self.alice_device_id in session.devices # Check that we can decrypt our own messages plaintext = { 'type': 'test', 'content': { 'test': 'test' }, } encrypted_event = device.megolm_build_encrypted_event( self.room, plaintext) event = { 'sender': self.alice, 'room_id': self.room_id, 'content': encrypted_event, 'type': 'm.room.encrypted', 'origin_server_ts': 1, 'event_id': 1 } device.megolm_decrypt_event(event) assert event['content'] == plaintext['content'] @responses.activate @pytest.mark.usefixtures('olm_session_with_alice') def test_megolm_share_session_with_new_devices(self, device): responses.add(responses.PUT, self.to_device_url, json={}) session = MegolmOutboundSession() device.megolm_outbound_sessions[self.room_id] = session user_devices = {self.alice: [self.alice_device_id]} device.megolm_share_session_with_new_devices(self.room, user_devices, session) assert self.alice_device_id in session.devices assert len(responses.calls) == 1 device.megolm_share_session_with_new_devices(self.room, user_devices, session) assert len(responses.calls) == 1 def test_megolm_get_recipients(self, device): device.device_keys[self.alice][ self.alice_device_id] = self.alice_device user_devices, _ = device.megolm_get_recipients(self.room) assert user_devices == {self.alice: [self.alice_device_id]} session = MegolmOutboundSession() device.megolm_outbound_sessions[self.room_id] = session user_devices, removed = device.megolm_get_recipients( self.room, session) assert user_devices == { self.alice: [self.alice_device_id] } and not removed self.alice_device.blacklisted = True _, removed = device.megolm_get_recipients(self.room, session) assert not removed session.add_device(self.alice_device_id) _, removed = device.megolm_get_recipients(self.room, session) assert removed and self.room_id not in device.megolm_outbound_sessions self.alice_device.blacklisted = False self.room.verify_devices = True with pytest.raises(E2EUnknownDevices) as e: device.megolm_get_recipients(self.room) assert e.value.user_devices == {self.alice: [self.alice_device]} self.room.verify_devices = False @responses.activate @pytest.mark.usefixtures('olm_session_with_alice') def test_megolm_build_encrypted_event(self, device): responses.add(responses.PUT, self.to_device_url, json={}) device.device_list.tracked_user_ids.add(self.alice) event = {'type': 'm.room.message', 'content': {'body': 'test'}} self.room.rotation_period_msgs = 1 device.megolm_build_encrypted_event(self.room, event) device.megolm_build_encrypted_event(self.room, event) session = device.megolm_outbound_sessions[self.room_id] session.encrypt('test') device.megolm_build_encrypted_event(self.room, event) assert device.megolm_outbound_sessions[self.room_id].id != session.id def test_megolm_remove_outbound_session(self, device): session = MegolmOutboundSession() device.megolm_outbound_sessions[self.room_id] = session device.megolm_remove_outbound_session(self.room_id) device.megolm_remove_outbound_session(self.room_id) @responses.activate @pytest.mark.usefixtures('olm_session_with_alice') def test_send_encrypted_message(self, device): message_url = HOSTNAME + MATRIX_V2_API_PATH + \ '/rooms/{}/send/m.room.encrypted/1'.format(quote(self.room.room_id)) responses.add(responses.PUT, message_url, json={}) session = MegolmOutboundSession() session.add_device(self.alice_device_id) device.megolm_outbound_sessions[self.room_id] = session device.send_encrypted_message(self.room, {'test': 'test'}) def test_megolm_add_inbound_session(self, device): session = MegolmOutboundSession() with pytest.raises(ValueError): device.megolm_add_inbound_session(self.room_id, self.alice_curve_key, self.alice_ed_key, session.id, 'wrong') assert device.megolm_add_inbound_session(self.room_id, self.alice_curve_key, self.alice_ed_key, session.id, session.session_key) assert session.id in \ device.megolm_inbound_sessions[self.room_id][self.alice_curve_key] assert not device.megolm_add_inbound_session( self.room_id, self.alice_curve_key, self.alice_ed_key, session.id, session.session_key) with pytest.raises(ValueError): device.megolm_add_inbound_session(self.room_id, self.alice_curve_key, self.alice_ed_key, 'wrong', session.session_key) def test_handle_room_key_event(self, device): device.handle_room_key_event(example_room_key_event, self.alice_curve_key) assert self.room_id in device.megolm_inbound_sessions device.handle_room_key_event(example_room_key_event, self.alice_curve_key) event = deepcopy(example_room_key_event) event['content']['algorithm'] = 'wrong' device.handle_room_key_event(event, self.alice_curve_key) event = deepcopy(example_room_key_event) event['content']['session_id'] = 'wrong' device.handle_room_key_event(event, self.alice_curve_key) def test_olm_handle_encrypted_event(self, device, alice_olm_device): content = example_room_key_event['content'] encrypted_event = alice_olm_device.olm_build_encrypted_event( 'm.room_key', content, self.user_id, self.device_id) event = { 'type': 'm.room.encrypted', 'content': encrypted_event, 'sender': self.alice } device.olm_handle_encrypted_event(event) # Decrypting the same event twice will trigger an error device.olm_handle_encrypted_event(event) # Forwarded key event encrypted_event = alice_olm_device.olm_build_encrypted_event( 'm.forwarded_room_key', content, self.user_id, self.device_id) event = { 'type': 'm.room.encrypted', 'content': encrypted_event, 'sender': self.alice } device.olm_handle_encrypted_event(event) # Unhandled event encrypted_event = alice_olm_device.olm_build_encrypted_event( 'm.other', content, self.user_id, self.device_id) event = { 'type': 'm.room.encrypted', 'content': encrypted_event, 'sender': self.alice } device.olm_handle_encrypted_event(event) # Simulate redacted event event['content'].pop('algorithm') device.olm_handle_encrypted_event(event) def test_megolm_decrypt_event(self, device): out_session = MegolmOutboundSession() plaintext = { 'content': { "test": "test" }, 'type': 'm.text', } ciphertext = out_session.encrypt(json.dumps(plaintext)) content = { 'ciphertext': ciphertext, 'session_id': out_session.id, 'sender_key': self.alice_curve_key, 'algorithm': 'm.megolm.v1.aes-sha2', 'device_id': self.alice_device_id, } event = { 'sender': self.alice, 'room_id': self.room_id, 'content': content, 'type': 'm.room.encrypted', 'origin_server_ts': 1, 'event_id': 1 } with pytest.raises(UnableToDecryptError): device.megolm_decrypt_event(event) session_key = out_session.session_key in_session = MegolmInboundSession(session_key, self.alice_ed_key) sessions = device.megolm_inbound_sessions[self.room_id] sessions[self.alice_curve_key][in_session.id] = in_session # Unknown message index with pytest.raises(RuntimeError): device.megolm_decrypt_event(event) ciphertext = out_session.encrypt(json.dumps(plaintext)) event['content']['ciphertext'] = ciphertext device.megolm_decrypt_event(event) assert event['content'] == plaintext['content'] # No replay attack event['content'] = content device.megolm_decrypt_event(event) assert event['content'] == plaintext['content'] # Replay attack event['content'] = content event['event_id'] = 2 with pytest.raises(RuntimeError): device.megolm_decrypt_event(event) event['event_id'] = 1 # Device verification device.device_keys[self.alice][ self.alice_device_id] = self.alice_device event['content'] = content # Unverified device.megolm_decrypt_event(event) assert event['content'] == plaintext['content'] assert isinstance(event, dict) event['content'] = content # Verified self.alice_device.verified = True decrypted_event = device.megolm_decrypt_event(event) assert decrypted_event['content'] == plaintext['content'] assert isinstance(decrypted_event, VerifiedEvent) in_session = MegolmInboundSession(session_key, self.alice_curve_key) sessions = device.megolm_inbound_sessions[self.room_id] sessions[self.alice_curve_key][in_session.id] = in_session # Wrong signing key with pytest.raises(RuntimeError): device.megolm_decrypt_event(event) self.alice_device.verified = False event['content']['algorithm'] = 'wrong' with pytest.raises(RuntimeError): device.megolm_decrypt_event(event) event['content'].pop('algorithm') event['type'] = 'encrypted' device.megolm_decrypt_event(event) assert event['type'] == 'encrypted'