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
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
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 load_outbound_sessions(self, sessions): """Loads all saved outbound Megolm sessions. Also loads the devices each are shared with. Args: sessions (dict): A map from room_id to a ``MegolmOutboundSession`` object, which will be populated. """ c = self.conn.cursor() rows = c.execute( 'SELECT * FROM megolm_outbound_sessions WHERE device_id=?', (self.device_id, )) for row in rows.fetchall(): device_ids = c.execute( 'SELECT user_device_id FROM megolm_outbound_devices WHERE device_id=? ' 'AND room_id=?', (self.device_id, row['room_id'])) devices = {device_id[0] for device_id in device_ids} max_age_s = row['max_age_s'] max_age = timedelta(seconds=max_age_s) session = MegolmOutboundSession.from_pickle( bytes(row['session']), devices, max_age, row['max_messages'], row['creation_time'], row['message_count'], self.pickle_key) sessions[row['room_id']] = session c.close()
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)
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_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
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 get_outbound_session(self, room_id, sessions=None): """Gets a saved outbound Megolm session. Also loads the devices it is shared with. Args: room_id (str): The room corresponding to the session. sessions (dict): Optional. A map from room_id to a :class:`.MegolmOutboundSession` object, to which the session will be added. Returns: :class:`.MegolmOutboundSession` object, or ``None`` if the session was not found. """ c = self.conn.cursor() c.execute( 'SELECT * FROM megolm_outbound_sessions WHERE device_id=? AND room_id=?', (self.device_id, room_id)) try: row = c.fetchone() session_data = bytes(row['session']) except TypeError: c.close() return None device_ids = c.execute( 'SELECT user_device_id FROM megolm_outbound_devices WHERE device_id=? ' 'AND room_id=?', (self.device_id, room_id)) devices = {device_id[0] for device_id in device_ids} c.close() max_age_s = row['max_age_s'] max_age = timedelta(seconds=max_age_s) session = MegolmOutboundSession.from_pickle( session_data, devices, max_age, row['max_messages'], row['creation_time'], row['message_count'], self.pickle_key) if sessions is not None: sessions[room_id] = session return session
def megolm_start_session(self, room, user_devices): """Start a megolm session in a room, and share it with its members. Args: room (Room): The room to use. user_devices (dict): Map from user id to a list of device ids. The session will be shared with those devices. Returns: The newly created session. """ session = MegolmOutboundSession(max_age=room.rotation_period_ms, max_messages=room.rotation_period_msgs) self.megolm_outbound_sessions[room.room_id] = session logger.info('Starting a new Meglom outbound session %s in %s.', session.id, room.room_id) self.db.remove_outbound_session(room.room_id) self.db.save_outbound_session(room.room_id, session) self.megolm_share_session(room.room_id, user_devices, session) # Store a corresponding inbound session, so that we can decrypt our own messages self.megolm_add_inbound_session( room.room_id, self.curve25519, self.ed25519, session.id, session.session_key) return session
def test_megolm_outbound_session(): session = MegolmOutboundSession() assert session.max_messages == 100 assert session.max_age == timedelta(days=7) session = MegolmOutboundSession(max_messages=1, max_age=100000) assert session.max_messages == 1 assert session.max_age == timedelta(milliseconds=100000) assert not session.devices session.add_device('test') assert 'test' in session.devices session.add_devices({'test2', 'test3'}) assert 'test2' in session.devices and 'test3' in session.devices assert not session.should_rotate() session.encrypt('message') assert session.should_rotate() session.max_messages = 2 assert not session.should_rotate() session.creation_time = datetime.now() - timedelta(milliseconds=100000) assert session.should_rotate()
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'
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)