commands: Add a command for interactive key verification.

This commit is contained in:
Damir Jelić 2019-05-05 23:23:14 +02:00
parent 38fa6d4063
commit 9f01a05617
3 changed files with 123 additions and 31 deletions

View file

@ -23,14 +23,14 @@ from builtins import str
from future.moves.itertools import zip_longest from future.moves.itertools import zip_longest
from collections import defaultdict from collections import defaultdict
from functools import partial from functools import partial
from nio import EncryptionError from nio import EncryptionError, LocalProtocolError
from . import globals as G from . import globals as G
from .colors import Formatted from .colors import Formatted
from .globals import SERVERS, W, UPLOADS, SCRIPT_NAME from .globals import SERVERS, W, UPLOADS, SCRIPT_NAME
from .server import MatrixServer from .server import MatrixServer
from .utf import utf8_decode 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 from .uploads import UploadsBuffer, Upload
@ -152,6 +152,18 @@ class WeechatCommandParser(object):
import_parser.add_argument("file") import_parser.add_argument("file")
import_parser.add_argument("passphrase") 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) return WeechatCommandParser._run_parser(parser, args)
@staticmethod @staticmethod
@ -385,15 +397,17 @@ def hook_commands():
"blacklist <user-id> <device-id> ||" "blacklist <user-id> <device-id> ||"
"unverify <user-id> <device-id> ||" "unverify <user-id> <device-id> ||"
"verify <user-id> <device-id> ||" "verify <user-id> <device-id> ||"
"verification start|accept|cancel|confirm <user-id> <device-id> ||"
"export <file-name> <passphrase> ||" "export <file-name> <passphrase> ||"
"import <file-name> <passphrase>" "import <file-name> <passphrase>"
), ),
# Description # Description
(" info: show info about known devices and their keys\n" (" info: show info about known devices and their keys\n"
" blacklist: blacklist a device\n" " blacklist: blacklist a device\n"
"unblacklist: unblacklist a device\n" " unblacklist: unblacklist a device\n"
" unverify: unverify a device\n" " unverify: unverify a device\n"
" verify: verify a device\n" " verify: verify a device\n"
"verification: manage interactive device verification\n"
" export: export encryption keys\n" " export: export encryption keys\n"
" import: import encryption keys\n\n" " import: import encryption keys\n\n"
"Examples:" "Examples:"
@ -406,6 +420,7 @@ def hook_commands():
'unblacklist %(olm_user_ids) %(olm_devices) ||' 'unblacklist %(olm_user_ids) %(olm_devices) ||'
'unverify %(olm_user_ids) %(olm_devices) ||' 'unverify %(olm_user_ids) %(olm_devices) ||'
'verify %(olm_user_ids) %(olm_devices) ||' 'verify %(olm_user_ids) %(olm_devices) ||'
'verification start|accept|cancel|confirm %(olm_user_ids) %(olm_devices) ||'
'export %(filename) ||' 'export %(filename) ||'
'import %(filename)' 'import %(filename)'
), ),
@ -710,6 +725,52 @@ def olm_import_command(server, args):
server.info("Succesfully imported keys") 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 @utf8_decode
def matrix_olm_command_cb(data, buffer, args): def matrix_olm_command_cb(data, buffer, args):
def command(server, 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) olm_blacklist_command(server, parsed_args)
elif parsed_args.subcommand == "unblacklist": elif parsed_args.subcommand == "unblacklist":
olm_unblacklist_command(server, parsed_args) olm_unblacklist_command(server, parsed_args)
elif parsed_args.subcommand == "verification":
olm_sas_command(server, parsed_args)
else: else:
message = ("{prefix}matrix: Command not implemented.".format( message = ("{prefix}matrix: Command not implemented.".format(
prefix=W.prefix("error"))) prefix=W.prefix("error")))

View file

@ -207,7 +207,7 @@ def matrix_olm_device_completion_cb(data, completion_item, buffer, completion):
if len(fields) < 2: if len(fields) < 2:
return W.WEECHAT_RC_OK return W.WEECHAT_RC_OK
user = fields[1] user = fields[-1]
if user not in device_store.users: if user not in device_store.users:
return W.WEECHAT_RC_OK return W.WEECHAT_RC_OK

View file

@ -378,17 +378,14 @@ class MatrixServer(object):
) )
def key_verification_cb(self, event): def key_verification_cb(self, event):
# TODO don't accept the verification automatically.
if isinstance(event, KeyVerificationStart): if isinstance(event, KeyVerificationStart):
self.info_highlight("{} via {} has started a key verification " self.info_highlight("{user} via {device} has started a key "
"process.".format( "verification process.\n"
event.sender, "To accept use /olm verification "
event.from_device "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): elif isinstance(event, KeyVerificationKey):
sas = self.client.key_verifications.get(event.transaction_id, None) 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) desc = u"".join(d.center(centered_width) for d in descriptions)
short_string = u"\n".join([emoji_str, desc]) short_string = u"\n".join([emoji_str, desc])
self.info_highlight(u"Short authentication string for {} via {}:\n" self.info_highlight(u"Short authentication string for "
u"{}".format( u"{user} via {device}:\n{string}\n"
device.user_id, u"Confirm that the strings match with "
device.id, u"/olm verification confirm {user} "
short_string u"{device}".format(
user=device.user_id,
device=device.id,
string=short_string
)) ))
elif isinstance(event, KeyVerificationMac): elif isinstance(event, KeyVerificationMac):
try: try:
self.accept_short_auth_string(event.transaction_id) sas = self.client.key_verifications[event.transaction_id]
except LocalProtocolError as e: except KeyError:
self.info(e) 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): def update_option(self, option, option_name):
if option_name == "address": if option_name == "address":
@ -1300,18 +1309,38 @@ class MatrixServer(object):
room_buffer.undecrypted_events.remove(undecrypted_event) room_buffer.undecrypted_events.remove(undecrypted_event)
room_buffer.replace_undecrypted_line(event) room_buffer.replace_undecrypted_line(event)
def accept_key_verification(self, event): def start_verification(self, device):
_, request = self.client.accept_key_verification(event.transaction_id) _, 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) self.send(request)
def to_device(self, message): def to_device(self, message):
_, request = self.client.to_device(message) _, request = self.client.to_device(message)
self.send(request) self.send(request)
def accept_short_auth_string(self, transaction_id): def confirm_sas(self, sas):
_, request = self.client.accept_short_auth_string(transaction_id) _, request = self.client.accept_short_auth_string(sas.transaction_id)
self.send(request) 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): def _handle_sync(self, response):
# we got the same batch again, nothing to do # we got the same batch again, nothing to do
if self.next_batch == response.next_batch: if self.next_batch == response.next_batch: