File: wormholereceive.py

package info (click to toggle)
gnome-keysign 1.3.0-5.1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,460 kB
  • sloc: python: 5,143; xml: 126; makefile: 33; sh: 16
file content (156 lines) | stat: -rw-r--r-- 5,744 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/env python
#    Copyright 2017 Ludovico de Nittis <aasonykk+gnome@gmail.com>
#
#    This file is part of GNOME Keysign.
#
#    GNOME Keysign is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    GNOME Keysign is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with GNOME Keysign.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import unicode_literals
import logging

from twisted.internet.defer import inlineCallbacks, returnValue
from wormhole.cli.public_relay import RENDEZVOUS_RELAY
from wormhole.errors import WrongPasswordError, LonelyError, TransferError
import wormhole
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
if __name__ == "__main__":
    from twisted.internet import gtk3reactor
    gtk3reactor.install()
from twisted.internet import reactor

from .gpgmeh import fingerprint_from_keydata
from .i18n import _
from .util import decode_message, encode_message, parse_barcode, mac_verify

log = logging.getLogger(__name__)


class WormholeReceive:
    def __init__(self, code, mac=None, app_id=None):
        self.w = None
        # Check if the given code is a barcode or directly the wormhole code
        parsed = parse_barcode(code).get("WORM", [None])[0]
        if parsed:
            self.code = parsed
        else:
            self.code = code
        if app_id:
            self.app_id = app_id
        else:
            # the following id is needed for interoperability with wormhole cli
            self.app_id = "lothar.com/wormhole/text-or-file-xfer"
        self.mac = mac

    @inlineCallbacks
    def start(self):
        log.info("Wormhole: Trying to receive a message with code: %s", self.code)

        self.stop()
        self.w = wormhole.create(self.app_id, RENDEZVOUS_RELAY, reactor)
        # The following mod is required for Python 2 support
        self.w.set_code("%s" % str(self.code))

        try:
            message = yield self.w.get_message()
            m = decode_message(message)
            key_data = None
            offer = m.get("offer", None)
            if offer:
                key_data = offer.get("message", None)
            if key_data:
                log.info("Message received: %s", key_data)
                if self._is_verified(key_data.encode("utf-8")):
                    log.debug("MAC is valid")
                    success = True
                    message = ""
                    # send a reply with a message ack, this also ensures wormhole cli interoperability
                    reply = {"answer": {"message_ack": "ok"}}
                    reply_encoded = encode_message(reply)
                    self.w.send_message(reply_encoded)
                    returnValue((key_data.encode("utf-8"), success, message))
                else:
                    log.warning("The received key has a different MAC")
                    self._reply_error(_("Wrong message authentication code"))
                    self._handle_failure(WrongPasswordError())
            else:
                log.info("Unrecognized message: %s", m)
                self._reply_error("Unrecognized message")
                self._handle_failure(TransferError())
        except WrongPasswordError as wpe:
            log.info("A wrong password has been used")
            self._handle_failure(wpe)
        except LonelyError as le:
            log.info("Closed the connection before we found anyone")
            self._handle_failure(le)

    def _is_verified(self, key_data):
        if self.mac is None:
            # Currently the MAC is not mandatory
            verified = True
        else:
            mac_key = fingerprint_from_keydata(key_data)
            verified = mac_verify(mac_key.encode('ascii'), key_data, self.mac)
        return verified

    def _reply_error(self, error_message):
        reply = {"error": error_message}
        reply_encoded = encode_message(reply)
        self.w.send_message(reply_encoded)

    @inlineCallbacks
    def stop(self):
        if self.w:
            try:
                yield self.w.close()
            except Exception as e:
                print(e)

    @staticmethod
    def _handle_failure(error):
        success = False
        key_data = None
        returnValue((key_data, success, type(error)))


def main(args):
    log.debug('Running main with args: %s', args)
    if not args:
        raise ValueError("You must provide an argument with the wormhole code")

    @inlineCallbacks
    def receive_key(w_code):
        receive = WormholeReceive(w_code)
        msg_tuple = yield receive.start()
        key_data, success, message = msg_tuple
        if success:
            print("key received:\n")
            print(key_data.decode("utf-8"))
        else:
            print(message)
        # Workaround for the send_message reply (until we find a better solution).
        # If we simply call reactor.stop() the send_message() will never be
        # completed. I think this happens because we are always in the reactor
        # thread and send_message is waiting for a context switch.
        reactor.callLater(1, reactor.stop)

    print("Trying to download the key, please wait")
    code = args[0]
    receive_key(code)
    reactor.run()

if __name__ == "__main__":
    import sys
    main(sys.argv[1:])