Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
    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'})
Ejemplo n.º 4
0
    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()
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
    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)
Ejemplo n.º 9
0
    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
Ejemplo n.º 10
0
    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
Ejemplo n.º 11
0
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()
Ejemplo n.º 12
0
    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'
Ejemplo n.º 13
0
 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)