encryption: Initial encrypted message sending support.

This commit is contained in:
poljar (Damir Jelić) 2018-05-11 13:03:42 +02:00
parent 8a3bda9797
commit 642e518464
5 changed files with 228 additions and 3 deletions

View file

@ -94,6 +94,19 @@ class MatrixClient:
return HttpRequest(RequestType.GET, self.host, path) return HttpRequest(RequestType.GET, self.host, path)
def room_encrypted_message(self, room_id, content):
# type: (str, Dict[Any, Any]) -> HttpRequest
query_parameters = {"access_token": self.access_token}
path = ("{api}/rooms/{room}/send/m.room.encrypted/"
"{tx_id}?{query_parameters}").format(
api=MATRIX_API_PATH,
room=quote(room_id),
tx_id=quote(str(self._get_txn_id())),
query_parameters=urlencode(query_parameters))
return HttpRequest(RequestType.PUT, self.host, path, content)
def room_send_message(self, def room_send_message(self,
room_id, room_id,
message_type, message_type,
@ -307,6 +320,18 @@ class MatrixClient:
return HttpRequest(RequestType.POST, self.host, path, content) return HttpRequest(RequestType.POST, self.host, path, content)
def to_device(self, event_type, content):
query_parameters = {"access_token": self.access_token}
path = ("{api}/sendToDevice/{event_type}/{tx_id}?"
"{query_parameters}").format(
api=MATRIX_API_PATH,
event_type=event_type,
tx_id=quote(str(self._get_txn_id())),
query_parameters=urlencode(query_parameters))
return HttpRequest(RequestType.PUT, self.host, path, content)
def mxc_to_http(self, mxc): def mxc_to_http(self, mxc):
# type: (str) -> str # type: (str) -> str
url = urlparse(mxc) url = urlparse(mxc)
@ -648,3 +673,47 @@ class MatrixKeyClaimMessage(MatrixMessage):
server) server)
return self._decode(server, object_hook) return self._decode(server, object_hook)
class MatrixToDeviceMessage(MatrixMessage):
def __init__(self, client, to_device_dict):
data = {
"content": to_device_dict,
"event_type": "m.room.encrypted"
}
MatrixMessage.__init__(self, client.to_device, data)
def decode_body(self, server):
object_hook = partial(MatrixEvents.MatrixToDeviceEvent.from_dict,
server)
return self._decode(server, object_hook)
class MatrixEncryptedMessage(MatrixMessage):
def __init__(self,
client,
room_id,
formatted_message,
content):
self.room_id = room_id
self.formatted_message = formatted_message
data = {
"room_id": self.room_id,
"content": content
}
MatrixMessage.__init__(self, client.room_encrypted_message, data)
def decode_body(self, server):
object_hook = partial(
MatrixEvents.MatrixSendEvent.from_dict,
server,
self.room_id,
self.formatted_message,
)
return self._decode(server, object_hook)

View file

@ -34,7 +34,7 @@ import matrix.globals
try: try:
from olm.account import Account, OlmAccountError from olm.account import Account, OlmAccountError
from olm.session import (Session, InboundSession, OlmSessionError, from olm.session import (Session, InboundSession, OlmSessionError,
OlmPreKeyMessage) OlmMessage, OlmPreKeyMessage)
from olm.group_session import ( from olm.group_session import (
InboundGroupSession, InboundGroupSession,
OutboundGroupSession, OutboundGroupSession,
@ -313,6 +313,29 @@ class Olm():
except OlmSessionError: except OlmSessionError:
return None return None
def group_encrypt(self, room_id, plaintext_dict):
# type: (str, Dict[str, str]) -> Dict[str, str], Bool
is_new = False
plaintext_dict["room_id"] = room_id
if not room_id in self.outbound_group_sessions:
self.create_outbound_group_session(room_id)
is_new = True
session = self.outbound_group_sessions[room_id]
ciphertext = session.encrypt(Olm._to_json(plaintext_dict))
payload_dict = {
"algorithm": "m.megolm.v1.aes-sha2",
"sender_key": self.account.identity_keys()["curve25519"],
"ciphertext": ciphertext,
"session_id": session.id,
"device_id": self.device_id
}
return payload_dict, is_new
@encrypt_enabled @encrypt_enabled
def group_decrypt(self, room_id, session_id, ciphertext): def group_decrypt(self, room_id, session_id, ciphertext):
if session_id not in self.inbound_group_sessions[room_id]: if session_id not in self.inbound_group_sessions[room_id]:
@ -326,6 +349,69 @@ class Olm():
return plaintext return plaintext
def share_group_session(self, room_id, own_id, users):
group_session = self.outbound_group_sessions[room_id]
key_content = {
"algorithm": "m.megolm.v1.aes-sha2",
"room_id": room_id,
"session_id": group_session.id,
"session_key": group_session.session_key,
"chain_index": group_session.message_index
}
payload_dict = {
"type": "m.room_key",
"content": key_content,
# TODO we don't have the user_id in the Olm class
"sender": own_id,
"sender_device": self.device_id,
"keys": {
"ed25519": self.account.identity_keys()["ed25519"]
}
}
to_device_dict = {
"messages": {}
}
for user in users:
for key in self.device_keys[user]:
if key.device_id == self.device_id:
continue
device_payload_dict = payload_dict.copy()
# TODO sort the sessions
session = self.sessions[user][key.device_id][0]
device_payload_dict["recipient"] = user
device_payload_dict["recipient_keys"] = {
"ed25519": key.keys["ed25519"]
}
W.prnt("", pprint.pformat(device_payload_dict))
olm_message = session.encrypt(Olm._to_json(device_payload_dict))
olm_dict = {
"algorithm": "m.olm.v1.curve25519-aes-sha2",
"sender_key": self.account.identity_keys()["curve25519"],
"ciphertext": {
key.keys["curve25519"]: {
"type": (0 if isinstance(olm_message,
OlmPreKeyMessage) else 1),
"body": olm_message.ciphertext
}
}
}
to_device_dict["messages"][user] = {
key.device_id: olm_dict
}
return to_device_dict
# return {}
@classmethod @classmethod
@encrypt_enabled @encrypt_enabled
def from_session_dir(cls, user, device_id, session_path): def from_session_dir(cls, user, device_id, session_path):
@ -474,6 +560,16 @@ class Olm():
return signature return signature
@staticmethod
def _to_json(json_dict):
# type: (Dict[Any, Any]) -> str
return json.dumps(
json_dict,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True
)
@encrypt_enabled @encrypt_enabled
def mark_keys_as_published(self): def mark_keys_as_published(self):
self.account.mark_keys_as_published() self.account.mark_keys_as_published()

View file

@ -367,6 +367,23 @@ class MatrixKeyClaimEvent(MatrixEvent):
pass pass
class MatrixToDeviceEvent(MatrixEvent):
def __init__(self, server):
MatrixEvent.__init__(self, server)
@classmethod
def from_dict(cls, server, parsed_dict):
try:
if parsed_dict == {}:
return cls(server)
raise KeyError
except KeyError:
return MatrixErrorEvent.from_dict(server, ("Error sending to "
"device message"),
False, parsed_dict)
class MatrixBacklogEvent(MatrixEvent): class MatrixBacklogEvent(MatrixEvent):
def __init__(self, server, room_id, end_token, events): def __init__(self, server, room_id, end_token, events):

View file

@ -652,6 +652,11 @@ class RoomMemberJoin(RoomEvent):
# calculate room display name and set it as the buffer list name # calculate room display name and set it as the buffer list name
room_name = room.display_name(server.user_id) room_name = room.display_name(server.user_id)
# A user has joined an encrypted room, we need to check for new devices
if room.encrypted:
server.device_check_timestamp = None
W.buffer_set(buff, "short_name", room_name) W.buffer_set(buff, "short_name", room_name)

View file

@ -38,7 +38,9 @@ from matrix.api import (
MatrixSyncMessage, MatrixSyncMessage,
MatrixLoginMessage, MatrixLoginMessage,
MatrixKeyUploadMessage, MatrixKeyUploadMessage,
MatrixKeyQueryMessage MatrixKeyQueryMessage,
MatrixToDeviceMessage,
MatrixEncryptedMessage
) )
from matrix.encryption import Olm, EncryptionError, encrypt_enabled from matrix.encryption import Olm, EncryptionError, encrypt_enabled
@ -483,6 +485,7 @@ class MatrixServer:
if not room.encrypted: if not room.encrypted:
return return
# TODO don't send messages unless all the devices are verified
missing = self.olm.get_missing_sessions(room.users.keys()) missing = self.olm.get_missing_sessions(room.users.keys())
if missing: if missing:
@ -494,7 +497,42 @@ class MatrixServer:
# TODO claim keys for the missing user/device combinations # TODO claim keys for the missing user/device combinations
return return
# self.send_queue.append(message) body = {"msgtype": "m.text", "body": formatted_data.to_plain()}
if formatted_data.is_formatted():
body["format"] = "org.matrix.custom.html"
body["formatted_body"] = formatted_data.to_html()
plaintext_dict = {
"type": "m.room.message",
"content": body
}
W.prnt("", "matrix: Encrypting message")
payload_dict, session_is_new = self.olm.group_encrypt(
room_id,
plaintext_dict
)
if session_is_new:
to_device_dict = self.olm.share_group_session(
room_id,
self.user_id,
room.users.keys()
)
message = MatrixToDeviceMessage(self.client, to_device_dict)
W.prnt("", "matrix: Megolm session missing for room.")
self.send_queue.append(message)
message = MatrixEncryptedMessage(
self.client,
room_id,
formatted_data,
payload_dict
)
self.send_queue.append(message)
@encrypt_enabled @encrypt_enabled
def upload_keys(self, device_keys=False, one_time_keys=False): def upload_keys(self, device_keys=False, one_time_keys=False):