Add olm account loading/creation and a initial olm command.

This commit is contained in:
poljar (Damir Jelić) 2018-03-05 19:34:51 +01:00
parent 45e6f742ec
commit c52b7866c8
5 changed files with 210 additions and 0 deletions

View file

@ -36,6 +36,7 @@ from matrix.colors import Formatted
from matrix.utf import utf8_decode from matrix.utf import utf8_decode
from matrix.http import HttpResponse from matrix.http import HttpResponse
from matrix.api import MatrixSendMessage from matrix.api import MatrixSendMessage
from matrix.encryption import matrix_olm_command_cb
# Weechat searches for the registered callbacks in the scope of the main script # Weechat searches for the registered callbacks in the scope of the main script
# file, import the callbacks here so weechat can find them. # file, import the callbacks here so weechat can find them.

View file

@ -32,6 +32,7 @@ from matrix.utils import key_from_value, tags_from_line_data
from matrix.plugin_options import DebugType from matrix.plugin_options import DebugType
from matrix.server import MatrixServer from matrix.server import MatrixServer
from matrix.colors import Formatted from matrix.colors import Formatted
from matrix.encryption import matrix_hook_olm_command
def hook_commands(): def hook_commands():
@ -98,6 +99,8 @@ def hook_commands():
"matrix_me_command_cb", "matrix_me_command_cb",
"") "")
matrix_hook_olm_command()
W.hook_command_run('/topic', 'matrix_command_topic_cb', '') W.hook_command_run('/topic', 'matrix_command_topic_cb', '')
W.hook_command_run('/buffer clear', 'matrix_command_buf_clear_cb', '') W.hook_command_run('/buffer clear', 'matrix_command_buf_clear_cb', '')
W.hook_command_run('/join', 'matrix_command_join_cb', '') W.hook_command_run('/join', 'matrix_command_join_cb', '')

176
matrix/encryption.py Normal file
View file

@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
# Weechat Matrix Protocol Script
# Copyright © 2018 Damir Jelić <poljar@termina.org.uk>
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the
# above copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
from __future__ import unicode_literals
import os
# pylint: disable=redefined-builtin
from builtins import str
from functools import wraps
from future.moves.itertools import zip_longest
import matrix.globals
try:
from olm.account import Account, OlmAccountError
except ImportError:
matrix.globals.ENCRYPTION = False
from matrix.globals import W, SERVERS
from matrix.utf import utf8_decode
def own_buffer(f):
@wraps(f)
def wrapper(data, buffer, *args, **kwargs):
for server in SERVERS.values():
if buffer in server.buffers.values():
return f(server.name, buffer, *args, **kwargs)
elif buffer == server.server_buffer:
return f(server.name, buffer, *args, **kwargs)
return W.WEECHAT_RC_OK
return wrapper
def encrypt_enabled(f):
@wraps(f)
def wrapper(*args, **kwds):
if matrix.globals.ENCRYPTION:
return f(*args, **kwds)
return None
return wrapper
@encrypt_enabled
def matrix_hook_olm_command():
W.hook_command(
# Command name and short description
"olm",
"Matrix olm encryption command",
# Synopsis
("info all|blacklisted|private|unverified|verified <filter>||"
"blacklist <device-id> ||"
"unverify <device-id> ||"
"verify <device-id>"),
# Description
(" info: show info about known devices and their keys\n"
"blacklist: blacklist a device\n"
" unverify: unverify a device\n"
" verify: verify a device\n\n"
"Examples:\n"),
# Completions
('info all|blacklisted|private|unverified|verified ||'
'blacklist %(device_ids) ||'
'unverify %(device_ids) ||'
'verify %(device_ids)'),
# Function name
'matrix_olm_command_cb',
'')
def olm_cmd_parse_args(args):
split_args = args.split()
command = split_args.pop(0) if split_args else "info"
rest_args = split_args if split_args else []
return command, rest_args
def grouper(iterable, n, fillvalue=None):
"Collect data into fixed-length chunks or blocks"
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
args = [iter(iterable)] * n
return zip_longest(*args, fillvalue=fillvalue)
def partition_key(key):
groups = grouper(key, 4, " ")
return ' '.join(''.join(g) for g in groups)
@own_buffer
@utf8_decode
def matrix_olm_command_cb(server_name, buffer, args):
server = SERVERS[server_name]
command, args = olm_cmd_parse_args(args)
if not command or command == "info":
olm = server.olm
device_msg = (" - Device ID: {}\n".format(server.device_id)
if server.device_id else "")
id_key = partition_key(olm.account.identity_keys()["curve25519"])
fp_key = partition_key(olm.account.identity_keys()["ed25519"])
message = ("{prefix}matrix: Identity keys:\n"
" - User: {user}\n"
"{device_msg}"
" - Identity key: {id_key}\n"
" - Fingerprint key: {fp_key}\n").format(
prefix=W.prefix("network"),
user=server.user,
device_msg=device_msg,
id_key=id_key,
fp_key=fp_key)
W.prnt(server.server_buffer, message)
else:
message = ("{prefix}matrix: Command not implemented.".format(
prefix=W.prefix("error")))
W.prnt(server.server_buffer, message)
return W.WEECHAT_RC_OK
class EncryptionError(Exception):
pass
class Olm():
@encrypt_enabled
def __init__(self, server, account=None):
# type: (Server, Account) -> None
self.server = server
if account:
self.account = account
else:
self.account = Account()
@classmethod
@encrypt_enabled
def from_session_dir(cls, server):
# type: (Server) -> Olm
account_file_name = "{}_{}.account".format(server.user,
server.device_id)
session_path = server.get_session_path()
path = os.path.join(session_path, account_file_name)
try:
with open(path, "rb") as f:
pickle = f.read()
account = Account.from_pickle(pickle)
return cls(server, account)
except OlmAccountError as error:
raise EncryptionError(error)

View file

@ -31,3 +31,4 @@ except ImportError:
OPTIONS = PluginOptions() # type: PluginOptions OPTIONS = PluginOptions() # type: PluginOptions
SERVERS = dict() # type: Dict[str, MatrixServer] SERVERS = dict() # type: Dict[str, MatrixServer]
CONFIG = None # type: weechat.config CONFIG = None # type: weechat.config
ENCRYPTION = True # type: bool

View file

@ -35,6 +35,13 @@ from matrix.globals import W, SERVERS, OPTIONS
import matrix.api as API import matrix.api as API
from matrix.api import MatrixClient, MatrixSyncMessage, MatrixLoginMessage from matrix.api import MatrixClient, MatrixSyncMessage, MatrixLoginMessage
from matrix.encryption import Olm, EncryptionError
try:
FileNotFoundError
except NameError:
FileNotFoundError = IOError
class MatrixServer: class MatrixServer:
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
@ -48,6 +55,7 @@ class MatrixServer:
self.options = dict() # type: Dict[str, weechat.config] self.options = dict() # type: Dict[str, weechat.config]
self.device_name = "Weechat Matrix" # type: str self.device_name = "Weechat Matrix" # type: str
self.device_id = "" # type: str self.device_id = "" # type: str
self.olm = None # type: Olm
self.user = "" # type: str self.user = "" # type: str
self.password = "" # type: str self.password = "" # type: str
@ -126,6 +134,24 @@ class MatrixServer:
with open(path, 'w') as f: with open(path, 'w') as f:
f.write(self.device_id) f.write(self.device_id)
def _load_olm(self):
try:
self.olm = Olm.from_session_dir(self)
except FileNotFoundError:
message = ("{prefix}matrix: Creating new Olm identity for {user}"
" on {server} for device {device}.").format(
prefix=W.prefix("network"),
user=self.user,
server=self.name,
device=self.device_id)
W.prnt("", message)
self.olm = Olm(self)
except EncryptionError as error:
message = ("{prefix}matrix: Error loading Olm"
"account: {error}.").format(
prefix=W.prefix("error"), error=error)
W.prnt("", message)
def _create_options(self, config_file): def _create_options(self, config_file):
options = [ options = [
Option('autoconnect', 'boolean', '', 0, 0, 'off', Option('autoconnect', 'boolean', '', 0, 0, 'off',
@ -200,6 +226,9 @@ class MatrixServer:
self._load_device_id() self._load_device_id()
if self.device_id:
self._load_olm()
elif option_name == "password": elif option_name == "password":
value = W.config_string(option) value = W.config_string(option)
self.password = W.string_eval_expression(value, {}, {}, {}) self.password = W.string_eval_expression(value, {}, {}, {})