Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
b0fa2a6
track the device list of users and download keys
Zil0 May 24, 2018
c8496d3
add device tracking tests
Zil0 Jun 24, 2018
69d300e
track devices of users in encrypted rooms
Zil0 Jun 12, 2018
9aadf1b
build doc for crypto.device_list
Zil0 Jun 29, 2018
165d4f4
add olm encryption, decryption and session
Zil0 May 23, 2018
c689ec1
add olm tests
Zil0 Jun 25, 2018
eb43d6c
add olm_ensure_sessions
Zil0 May 30, 2018
d3896b4
add megolm outbound session support
Zil0 May 28, 2018
f18f9b2
delete outbound sessions on leave
Zil0 Jun 18, 2018
de73b9d
send encrypted group messages
Zil0 Jun 5, 2018
c07ee43
automatically send encrypted messages in encrypted rooms
Zil0 Jun 5, 2018
24b5a40
add megolm outbound tests
Zil0 Jun 27, 2018
4ce7cdf
handle m.room.encryption properties
Zil0 Jun 29, 2018
71e8c52
build doc for crypto.megolm_outbound_session
Zil0 Jun 29, 2018
2515d57
pass rotation parameters when enabling encryption
Zil0 Jul 20, 2018
e649a23
add megolm inbound session support
Zil0 Jun 8, 2018
a28b29d
automatically decrypt encrypted events
Zil0 Jun 4, 2018
a377517
add megolm inbound tests
Zil0 Jun 28, 2018
ca08e86
persist olm account
Zil0 Jun 14, 2018
16ed294
build doc for crypto.crypto_store
Zil0 Jun 29, 2018
55e8e09
add account persistence tests
Zil0 Jun 29, 2018
9e0ad6f
prevent tests from using database
Zil0 Jun 29, 2018
05823ea
persist olm sessions
Zil0 Jun 14, 2018
33a06d8
add olm persistence tests
Zil0 Jun 30, 2018
f8487bd
persist megolm inbound sessions
Zil0 Jun 14, 2018
4bc2473
add megolm inbound persistence tests
Zil0 Jul 1, 2018
93f408a
persist megolm outbound sessions
Zil0 Jun 14, 2018
0ed22c1
add outbound sessions persistence tests
Zil0 Jul 2, 2018
46405db
add device keys persistence
Zil0 Jul 4, 2018
64134d7
add device keys persistence tests
Zil0 Jul 4, 2018
1804f7a
ignore optional dependency appdirs when building doc
Zil0 Jul 4, 2018
41e35d1
general improvement to CryptoStore
Zil0 Jul 21, 2018
2d7b271
better primary keys in crypto store
Zil0 Jul 25, 2018
cddd13a
restore device ID from user ID in CryptoStore
Zil0 Aug 3, 2018
bb10897
turn on SQLite secure_delete
Zil0 Aug 7, 2018
9567772
add m.file missing required key
Zil0 Jul 6, 2018
77d36f4
add encrypted attachments dependencies
Zil0 Jul 6, 2018
db8f378
encrypted attachments support
Zil0 Jul 6, 2018
b236ea2
plug-in encrypted attachments
Zil0 Jul 6, 2018
8d4f284
automatically send encrypted m.file messages
Zil0 Aug 16, 2018
656005b
add Device class
Zil0 Jul 15, 2018
6bce7b4
add devices attribute to User
Zil0 Jul 22, 2018
0c76655
make OlmDevice subclass Device
Zil0 Jul 15, 2018
f2b25e8
better device keys handling
Zil0 Jul 15, 2018
b27dfa8
remember signing key when establishing Megolm Inbound Session
Zil0 Jul 28, 2018
6d31cce
configure device verification
Zil0 Jul 28, 2018
c1c8aca
persist verification info
Zil0 Jul 28, 2018
e4f417f
alert of unknown devices when sending encrypted messages
Zil0 Jul 28, 2018
b5e1b7a
add device verification checks
Zil0 Jul 28, 2018
0492e95
persist device upon verification
Zil0 Aug 3, 2018
ba0950a
add Device class docstring
Zil0 Aug 7, 2018
93d7b0e
add Device class tests
Zil0 Aug 7, 2018
cdffc9f
add get_fingerprint method to client
Zil0 Aug 9, 2018
0fdfdd6
fixup! add devices attribute to User
Zil0 Sep 14, 2018
d11a0e7
refactor crypto tests
Zil0 Aug 2, 2018
5606b92
fail to enable encryption on limited cache_level
Zil0 Aug 2, 2018
0564d21
add key sharing functionality
Zil0 Jul 25, 2018
e3e579f
persist forwarded chain
Zil0 Aug 9, 2018
e76c5ec
persist outgoing key requests
Zil0 Aug 7, 2018
ae8d11d
add key sharing tests
Zil0 Aug 9, 2018
4cdb526
add key export functions
Zil0 Aug 8, 2018
b1b888f
export keys from OlmDevice
Zil0 Aug 8, 2018
027ae79
add key export tests
Zil0 Aug 8, 2018
c9f4938
add import/export methods to client
Zil0 Aug 9, 2018
d30d5cb
add E2E friendly documentation
Zil0 Aug 10, 2018
150b007
add E2E sample code
Zil0 Aug 10, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions E2E_overview.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
Overview of end-to-end encryption in matrix-python-sdk
------------------------------------------------------

This SDK supports end-to-end encryption as specified in Matrix. The following is an
overview of the main available features.

Encryption is mostly automatic, and users are not expected to read past the `basic
usage`_ section.

.. contents::

Installation
============

Encryption requires `libolm`__, the official Matrix library that provides the necessary
cryptographic primitives. It is available in many Linux distribution repositories, and can
also be easily compiled from source.

__ https://matrix.org/git/olm

Encryption also comes with several optional dependencies, listed under the ``e2e`` group
in ``setup.py``.

Using pip, these can be installed by running ``pip install .[e2e]`` at the root of the
repository.

Encryption heavily rely on an underlying database, in order to work seamlessly across
restarts. This is implemented using SQLite and the sqlite3 module of the standard Python
library. Users do not not have to worry about this, and the database location is platform
dependent (and is displayed on start-up via an info log line). For advanced usage, see
`overriding the crypto store`_.


Basic usage
===========

Encryption support is disabled by default. Enabling it is done when instantiating
``MatrixClient``, as follow:

.. code:: python

client = MatrixClient(HOSTNAME, encryption=True)

.. note::

When enabling encryption in an already existing project, you will notice that a lot of
logging messages appear. Most of those can be safely ignored. For instance, warning
messages on first sync simply mean that the client is unable to decrypt old messages
it didn't receive the keys for, as there are anterior to the encryption enabling.

Device IDs
~~~~~~~~~~

When using encryption, a user **should** reuse device IDs, as they are associated with
a fingerprint key that should not change across restart, in most cases. The complete
rationale is explained `here`__.

__ https://matrix.org/docs/guides/e2e_implementation.html#devices

A user can keep track of device IDs by specifying them at login, or can delegate it to the
SDK, as follow:

.. code:: python

client = MatrixClient(HOSTNAME, encryption=True, restore_device_id=True)

On first launch, the client will store the device ID returned by the homeserver in the
same database used to store encryption keys. On subsequent launches, the device ID will be
retrieved from the user ID at login.

.. note::

When logging in with ``restore_device_id`` turned on, you must supply a full user ID (eg ``@test:matrix.org``), not just a username (eg ``test``).

When using this, the need to reset the device ID automatically associated with a user ID
may arise. This can be done by explicitly specifying a device ID at login, or simply by
removing the database (consider using ``shred`` over ``rm``). Both of these methods will
delete all the encryption data associated with the previous device, as none can be safely
reused as-is with a new one. Hence, before doing this, a user might want to `export
encryption keys`_.

.. note::

Refer to ``samples/e2e_overview.py`` for more example code.


Advanced usage
==============

Several options are available in order to customize some behaviors, or to enable
additional features. These are abundantly documented via docstrings, and the following
subsections aim at showing some examples.

Device verification
~~~~~~~~~~~~~~~~~~~

A major feature of end-to-end encryption is to make sure that the sender of a message is
the actual sender, and not an usurper.

In order to allow other users to verify the current device, its fingerprint should be
displayed. This is done by calling ``client.get_fingerprint()``.

Device verification is disabled by default. It can be enabled globally by passing
``verify_devices=True`` when instantiating ``MatrixClient``, or on a per-room basis by
doing ``room.verify_devices = True``.

Once device verification is enabled in a room, sending messages to it will raise
``E2EUnknownDevices`` if there are some never seen before devices. A user should inspect
the ``user_devices`` attribute of this exception, and for each devices it contains, do
either:

- ``device.verified = True`` if the device can be verified. New checks will be enabled
to ensure that every subsequent messages received from this device actually come from
it.
- ``device.blacklisted = True`` if decryption keys should never be shared with this
device.
- ``device.ignored = True`` if the device cannot be verified, and keys should be
sent to it anyway.

Those verifications are persisted in database.

.. note::

This section is incomplete (doesn't explain how to verify an event).

Key sharing
~~~~~~~~~~~

A feature of the protocol is to be able to request and receive encryption keys from other
users. The SDK implements only the sharing of keys with devices of the current Matrix
user.

Key sharing is disabled by default. A user has to implement non-trivial logic in order to
use it.

The automatic request of keys can be enabled by adding a listener using
``MatrixClient.add_key_forward_listener(callback)``. The callback should be used to be
notified when a new key arrives, and it is advised to carefully read the docstring of this
method. A client only wanting to silently request and receive keys can add a callback
which does nothing.

In order to reply to key requests, ``MatrixClient.add_key_request_listener(callback)``
should be used. Refer to the docstring for more info.

Encrypted attachments
~~~~~~~~~~~~~~~~~~~~~

.. TODO waiting for more convenient upload/download process

Export encryption keys
~~~~~~~~~~~~~~~~~~~~~~

A user may want to import or export the encryption keys used in rooms, in order to be able
to decrypt messages on a new device. This can be done by using the ``export_keys`` and
``import_keys`` methods of ``MatrixClient``.

Overriding the crypto store
~~~~~~~~~~~~~~~~~~~~~~~~~~~

In order to use another storage method, the SQLite storage can be replaced by subclassing
the class ``CryptoStore`` and carefully reimplementing all the methods, which are
thoroughly documented for this purpose. The new class can then be used as follow:

.. code:: python

client = MatrixClient(HOSTNAME, encryption=True, encryption_conf={'Store': NewClass})

Changing the database file location
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This feature is especially useful when wanting to run several instances of
``MatrixClient`` in multiple *processes* (threads should work fine). The SQLite database
cannot be shared between processes (at least not without proper locking, which would have
to be implemented). Then the easiest way is to have one database per process.

The ``CryptoStore`` class can be passed attributes ``db_path`` and ``db_name``.
Then, configuring the database to be stored as ``/foo/bar.db`` is done as follow:

.. code:: python

store_conf = {'db_path': '/foo/', 'db_name': 'bar.db'}
client = MatrixClient(HOSTNAME, encryption=True,
encryption_conf={'store_conf': store_conf})
3 changes: 2 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,5 @@
'Miscellaneous'),
]

autodoc_mock_imports = ["olm", "canonicaljson"]
autodoc_mock_imports = ["olm", "canonicaljson", "appdirs", "unpaddedbase64", "Crypto",
"Crypto.Cipher", "Crypto.Hash", "Crypto.Util"]
30 changes: 30 additions & 0 deletions docs/source/matrix_client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,33 @@ matrix_client.crypto
:members:
:undoc-members:
:show-inheritance:

.. automodule:: matrix_client.crypto.device_list
:members:
:undoc-members:
:show-inheritance:

.. automodule:: matrix_client.crypto.sessions
:members:
:undoc-members:
:show-inheritance:

.. automodule:: matrix_client.crypto.crypto_store
:members:
:undoc-members:
:show-inheritance:

.. automodule:: matrix_client.crypto.encrypt_attachments
:members:
:undoc-members:
:show-inheritance:

.. automodule:: matrix_client.crypto.verified_event
:members:
:undoc-members:
:show-inheritance:

.. automodule:: matrix_client.crypto.key_sharing
:members:
:undoc-members:
:show-inheritance:
62 changes: 42 additions & 20 deletions matrix_client/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,19 +329,43 @@ def redact_event(self, room_id, event_id, reason=None, txn_id=None, timestamp=No
# content_type can be a image,audio or video
# extra information should be supplied, see
# https://matrix.org/docs/spec/r0.0.1/client_server.html
def send_content(self, room_id, item_url, item_name, msg_type,
extra_information=None, timestamp=None):
def send_content(self, room_id, item_url, item_name, msg_type, filename=None,
extra_information=None, timestamp=None, encryption_info=None):
content_pack = self.get_content_body(item_url, item_name, msg_type, filename,
extra_information, encryption_info)
return self.send_message_event(room_id, "m.room.message", content_pack,
timestamp=timestamp)

def get_content_body(self, item_url, item_name, msg_type, filename=None,
extra_information=None, encryption_info=None):
if extra_information is None:
extra_information = {}

content_pack = {
"url": item_url,
"msgtype": msg_type,
"body": item_name,
"info": extra_information
}
return self.send_message_event(room_id, "m.room.message", content_pack,
timestamp=timestamp)
if msg_type == "m.file":
content_pack["filename"] = filename or item_name
if encryption_info:
encryption_info['url'] = item_url
content_pack['file'] = encryption_info
else:
content_pack['url'] = item_url
return content_pack

def get_location_body(self, geo_uri, name, thumb_url=None, thumb_info=None):
content_pack = {
"geo_uri": geo_uri,
"msgtype": "m.location",
"body": name,
}
if thumb_url:
content_pack["thumbnail_url"] = thumb_url
if thumb_info:
content_pack["thumbnail_info"] = thumb_info
return content_pack

# http://matrix.org/docs/spec/client_server/r0.2.0.html#m-location
def send_location(self, room_id, geo_uri, name, thumb_url=None, thumb_info=None,
Expand All @@ -356,15 +380,8 @@ def send_location(self, room_id, geo_uri, name, thumb_url=None, thumb_info=None,
thumb_info (dict): Metadata about the thumbnail, type ImageInfo.
timestamp (int): Set origin_server_ts (For application services only)
"""
content_pack = {
"geo_uri": geo_uri,
"msgtype": "m.location",
"body": name,
}
if thumb_url:
content_pack["thumbnail_url"] = thumb_url
if thumb_info:
content_pack["thumbnail_info"] = thumb_info
content_pack = self.get_location_body(
geo_uri, name, thumb_url, thumb_info)

return self.send_message_event(room_id, "m.room.message", content_pack,
timestamp=timestamp)
Expand Down Expand Up @@ -405,12 +422,11 @@ def send_notice(self, room_id, text_content, timestamp=None):
text_content (str): The m.notice body to send.
timestamp (int): Set origin_server_ts (For application services only)
"""
body = {
"msgtype": "m.notice",
"body": text_content
}
return self.send_message_event(room_id, "m.room.message", body,
timestamp=timestamp)
return self.send_message_event(
room_id, "m.room.message",
self.get_notice_body(text_content),
timestamp=timestamp
)

def get_room_messages(self, room_id, token, direction, limit=10, to=None):
"""Perform GET /rooms/{roomId}/messages.
Expand Down Expand Up @@ -675,6 +691,12 @@ def get_emote_body(self, text):
"body": text
}

def get_notice_body(self, text):
return {
"msgtype": "m.notice",
"body": text
}

def get_filter(self, user_id, filter_id):
return self._send("GET", "/user/{userId}/filter/{filterId}"
.format(userId=user_id, filterId=filter_id))
Expand Down
Loading