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.http import HttpResponse
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
# 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.server import MatrixServer
from matrix.colors import Formatted
from matrix.encryption import matrix_hook_olm_command
def hook_commands():
@ -98,6 +99,8 @@ def hook_commands():
"matrix_me_command_cb",
"")
matrix_hook_olm_command()
W.hook_command_run('/topic', 'matrix_command_topic_cb', '')
W.hook_command_run('/buffer clear', 'matrix_command_buf_clear_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
SERVERS = dict() # type: Dict[str, MatrixServer]
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
from matrix.api import MatrixClient, MatrixSyncMessage, MatrixLoginMessage
from matrix.encryption import Olm, EncryptionError
try:
FileNotFoundError
except NameError:
FileNotFoundError = IOError
class MatrixServer:
# pylint: disable=too-many-instance-attributes
@ -48,6 +55,7 @@ class MatrixServer:
self.options = dict() # type: Dict[str, weechat.config]
self.device_name = "Weechat Matrix" # type: str
self.device_id = "" # type: str
self.olm = None # type: Olm
self.user = "" # type: str
self.password = "" # type: str
@ -126,6 +134,24 @@ class MatrixServer:
with open(path, 'w') as f:
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):
options = [
Option('autoconnect', 'boolean', '', 0, 0, 'off',
@ -200,6 +226,9 @@ class MatrixServer:
self._load_device_id()
if self.device_id:
self._load_olm()
elif option_name == "password":
value = W.config_string(option)
self.password = W.string_eval_expression(value, {}, {}, {})