encryption: Initial encrypted message sending support.
This commit is contained in:
parent
8a3bda9797
commit
642e518464
5 changed files with 228 additions and 3 deletions
|
@ -94,6 +94,19 @@ class MatrixClient:
|
|||
|
||||
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,
|
||||
room_id,
|
||||
message_type,
|
||||
|
@ -307,6 +320,18 @@ class MatrixClient:
|
|||
|
||||
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):
|
||||
# type: (str) -> str
|
||||
url = urlparse(mxc)
|
||||
|
@ -648,3 +673,47 @@ class MatrixKeyClaimMessage(MatrixMessage):
|
|||
server)
|
||||
|
||||
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)
|
||||
|
|
|
@ -34,7 +34,7 @@ import matrix.globals
|
|||
try:
|
||||
from olm.account import Account, OlmAccountError
|
||||
from olm.session import (Session, InboundSession, OlmSessionError,
|
||||
OlmPreKeyMessage)
|
||||
OlmMessage, OlmPreKeyMessage)
|
||||
from olm.group_session import (
|
||||
InboundGroupSession,
|
||||
OutboundGroupSession,
|
||||
|
@ -313,6 +313,29 @@ class Olm():
|
|||
except OlmSessionError:
|
||||
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
|
||||
def group_decrypt(self, room_id, session_id, ciphertext):
|
||||
if session_id not in self.inbound_group_sessions[room_id]:
|
||||
|
@ -326,6 +349,69 @@ class Olm():
|
|||
|
||||
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
|
||||
@encrypt_enabled
|
||||
def from_session_dir(cls, user, device_id, session_path):
|
||||
|
@ -474,6 +560,16 @@ class Olm():
|
|||
|
||||
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
|
||||
def mark_keys_as_published(self):
|
||||
self.account.mark_keys_as_published()
|
||||
|
|
|
@ -367,6 +367,23 @@ class MatrixKeyClaimEvent(MatrixEvent):
|
|||
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):
|
||||
|
||||
def __init__(self, server, room_id, end_token, events):
|
||||
|
|
|
@ -652,6 +652,11 @@ class RoomMemberJoin(RoomEvent):
|
|||
|
||||
# calculate room display name and set it as the buffer list name
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -38,7 +38,9 @@ from matrix.api import (
|
|||
MatrixSyncMessage,
|
||||
MatrixLoginMessage,
|
||||
MatrixKeyUploadMessage,
|
||||
MatrixKeyQueryMessage
|
||||
MatrixKeyQueryMessage,
|
||||
MatrixToDeviceMessage,
|
||||
MatrixEncryptedMessage
|
||||
)
|
||||
|
||||
from matrix.encryption import Olm, EncryptionError, encrypt_enabled
|
||||
|
@ -483,6 +485,7 @@ class MatrixServer:
|
|||
if not room.encrypted:
|
||||
return
|
||||
|
||||
# TODO don't send messages unless all the devices are verified
|
||||
missing = self.olm.get_missing_sessions(room.users.keys())
|
||||
|
||||
if missing:
|
||||
|
@ -494,7 +497,42 @@ class MatrixServer:
|
|||
# TODO claim keys for the missing user/device combinations
|
||||
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
|
||||
def upload_keys(self, device_keys=False, one_time_keys=False):
|
||||
|
|
Loading…
Reference in a new issue