diff --git a/matrix/commands.py b/matrix/commands.py index bc20198..b9ca16e 100644 --- a/matrix/commands.py +++ b/matrix/commands.py @@ -23,14 +23,14 @@ from builtins import str from future.moves.itertools import zip_longest from collections import defaultdict from functools import partial -from nio import EncryptionError +from nio import EncryptionError, LocalProtocolError from . import globals as G from .colors import Formatted from .globals import SERVERS, W, UPLOADS, SCRIPT_NAME from .server import MatrixServer from .utf import utf8_decode -from .utils import key_from_value, tags_from_line_data +from .utils import key_from_value from .uploads import UploadsBuffer, Upload @@ -152,6 +152,18 @@ class WeechatCommandParser(object): import_parser.add_argument("file") import_parser.add_argument("passphrase") + sas_parser = subparsers.add_parser("verification") + sas_parser.add_argument( + "action", + choices=[ + "start", + "accept", + "confirm", + "cancel", + ]) + sas_parser.add_argument("user_id") + sas_parser.add_argument("device_id") + return WeechatCommandParser._run_parser(parser, args) @staticmethod @@ -385,17 +397,19 @@ def hook_commands(): "blacklist ||" "unverify ||" "verify ||" + "verification start|accept|cancel|confirm ||" "export ||" "import " ), # Description - (" info: show info about known devices and their keys\n" - " blacklist: blacklist a device\n" - "unblacklist: unblacklist a device\n" - " unverify: unverify a device\n" - " verify: verify a device\n" - " export: export encryption keys\n" - " import: import encryption keys\n\n" + (" info: show info about known devices and their keys\n" + " blacklist: blacklist a device\n" + " unblacklist: unblacklist a device\n" + " unverify: unverify a device\n" + " verify: verify a device\n" + "verification: manage interactive device verification\n" + " export: export encryption keys\n" + " import: import encryption keys\n\n" "Examples:" "\n /olm verify @example:example.com *" "\n /olm info all example*" @@ -406,6 +420,7 @@ def hook_commands(): 'unblacklist %(olm_user_ids) %(olm_devices) ||' 'unverify %(olm_user_ids) %(olm_devices) ||' 'verify %(olm_user_ids) %(olm_devices) ||' + 'verification start|accept|cancel|confirm %(olm_user_ids) %(olm_devices) ||' 'export %(filename) ||' 'import %(filename)' ), @@ -710,6 +725,52 @@ def olm_import_command(server, args): server.info("Succesfully imported keys") +def olm_sas_command(server, args): + try: + device_store = server.client.device_store + except LocalProtocolError: + server.error("The device store is not loaded") + return W.WEECHAT_RC_OK + + try: + device = device_store[args.user_id][args.device_id] + except KeyError: + server.error("Device {} of user {} not found".format( + args.user_id, + args.device_id + )) + return W.WEECHAT_RC_OK + + if device.deleted: + server.error("Device {} of user {} is deleted.".format( + args.user_id, + args.device_id + )) + return W.WEECHAT_RC_OK + + if args.action == "start": + server.start_verification(device) + elif args.action in ["accept", "confirm", "cancel"]: + sas = server.client.get_active_sas(args.user_id, args.device_id) + + if not sas: + server.error("No active key verification found for " + "device {} of user {}.".format( + args.user_id, + args.device_id + )) + return W.WEECHAT_RC_OK + + try: + if args.action == "accept": + server.accept_sas(sas) + elif args.action == "confirm": + server.confirm_sas(sas) + + except LocalProtocolError as e: + server.error(str(e)) + + @utf8_decode def matrix_olm_command_cb(data, buffer, args): def command(server, data, buffer, args): @@ -736,6 +797,8 @@ def matrix_olm_command_cb(data, buffer, args): olm_blacklist_command(server, parsed_args) elif parsed_args.subcommand == "unblacklist": olm_unblacklist_command(server, parsed_args) + elif parsed_args.subcommand == "verification": + olm_sas_command(server, parsed_args) else: message = ("{prefix}matrix: Command not implemented.".format( prefix=W.prefix("error"))) diff --git a/matrix/completion.py b/matrix/completion.py index 1a08b05..578309e 100644 --- a/matrix/completion.py +++ b/matrix/completion.py @@ -207,7 +207,7 @@ def matrix_olm_device_completion_cb(data, completion_item, buffer, completion): if len(fields) < 2: return W.WEECHAT_RC_OK - user = fields[1] + user = fields[-1] if user not in device_store.users: return W.WEECHAT_RC_OK diff --git a/matrix/server.py b/matrix/server.py index 7d23bd5..a4e5145 100644 --- a/matrix/server.py +++ b/matrix/server.py @@ -378,17 +378,14 @@ class MatrixServer(object): ) def key_verification_cb(self, event): - # TODO don't accept the verification automatically. if isinstance(event, KeyVerificationStart): - self.info_highlight("{} via {} has started a key verification " - "process.".format( - event.sender, - event.from_device + self.info_highlight("{user} via {device} has started a key " + "verification process.\n" + "To accept use /olm verification " + "accept {user} {device}".format( + user=event.sender, + device=event.from_device )) - try: - self.accept_key_verification(event) - except LocalProtocolError as e: - self.info(e) elif isinstance(event, KeyVerificationKey): sas = self.client.key_verifications.get(event.transaction_id, None) @@ -437,18 +434,30 @@ class MatrixServer(object): desc = u"".join(d.center(centered_width) for d in descriptions) short_string = u"\n".join([emoji_str, desc]) - self.info_highlight(u"Short authentication string for {} via {}:\n" - u"{}".format( - device.user_id, - device.id, - short_string + self.info_highlight(u"Short authentication string for " + u"{user} via {device}:\n{string}\n" + u"Confirm that the strings match with " + u"/olm verification confirm {user} " + u"{device}".format( + user=device.user_id, + device=device.id, + string=short_string )) elif isinstance(event, KeyVerificationMac): try: - self.accept_short_auth_string(event.transaction_id) - except LocalProtocolError as e: - self.info(e) + sas = self.client.key_verifications[event.transaction_id] + except KeyError: + return + + device = sas.other_olm_device + + if sas.verified: + self.info_highlight("Device {} of user {} succesfully " + "verified".format( + device.id, + device.user_id + )) def update_option(self, option, option_name): if option_name == "address": @@ -1300,18 +1309,38 @@ class MatrixServer(object): room_buffer.undecrypted_events.remove(undecrypted_event) room_buffer.replace_undecrypted_line(event) - def accept_key_verification(self, event): - _, request = self.client.accept_key_verification(event.transaction_id) + def start_verification(self, device): + _, request = self.client.start_key_verification(device) + self.send(request) + self.info("Starting an interactive device verification with " + "{} {}".format(device.user_id, device.id)) + + def accept_sas(self, sas): + _, request = self.client.accept_key_verification(sas.transaction_id) + self.send(request) + + def cancel_sas(self, sas): + _, request = self.client.cancel_key_verification(sas.transaction_id) self.send(request) def to_device(self, message): _, request = self.client.to_device(message) self.send(request) - def accept_short_auth_string(self, transaction_id): - _, request = self.client.accept_short_auth_string(transaction_id) + def confirm_sas(self, sas): + _, request = self.client.accept_short_auth_string(sas.transaction_id) self.send(request) + device = sas.other_olm_device + + if sas.verified: + self.info("Device {} of user {} succesfully verified".format( + device.id, + device.user_id + )) + else: + self.info("Waiting for {} to confirm...".format(device.user_id)) + def _handle_sync(self, response): # we got the same batch again, nothing to do if self.next_batch == response.next_batch: