From fae14ebe6a5ad4e265efec1a492f1eeb1f35e68f Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 24 Mar 2015 00:28:02 -0700 Subject: [PATCH] Add --verify (display/check key-verifier). Not entirely usable yet. To be useful, both sides must add -v. If the sender uses -v but the receiver doesn't, the receiver won't show the verification string, so the sender can't compare it to anything (and must either abort the transfer or accept it blindly). Maybe the receiver should show the verification string unconditionally. Maybe the sender should indicate (in unprotected plaintext, along with the PAKE message) whether the receiver should show it or not. --- src/wormhole/blocking/transcribe.py | 32 ++++++++++++++++++++---- src/wormhole/scripts/cmd_receive_file.py | 10 +++++++- src/wormhole/scripts/cmd_receive_text.py | 10 +++++++- src/wormhole/scripts/cmd_send_file.py | 16 +++++++++++- src/wormhole/scripts/cmd_send_text.py | 23 ++++++++++++++--- src/wormhole/scripts/runner.py | 2 ++ 6 files changed, 81 insertions(+), 12 deletions(-) diff --git a/src/wormhole/blocking/transcribe.py b/src/wormhole/blocking/transcribe.py index f3edfb7..a79f4ed 100644 --- a/src/wormhole/blocking/transcribe.py +++ b/src/wormhole/blocking/transcribe.py @@ -175,6 +175,8 @@ class Initiator(Common): self.wait = 0.5*SECOND self.timeout = 3*MINUTE self.side = "initiator" + self.key = None + self.verifier = None def get_code(self, code_length=2): self.channel_id = self._allocate() # allocate channel @@ -185,9 +187,18 @@ class Initiator(Common): self._post_pake() return self.code + def _wait_for_key(self): + if not self.key: + key = self._get_pake([]) + self.key = key + self.verifier = self.derive_key(self.appid+b":Verifier") + + def get_verifier(self): + self._wait_for_key() + return self.verifier + def get_data(self, outbound_data): - key = self._get_pake([]) - self.key = key + self._wait_for_key() try: outbound_key = self.derive_key(b"sender") outbound_encrypted = self._encrypt_data(outbound_key, outbound_data) @@ -216,6 +227,8 @@ class Receiver(Common): self.side = "receiver" self.code = None self.channel_id = None + self.key = None + self.verifier = None def list_channels(self): r = requests.get(self.relay + "list") @@ -237,12 +250,21 @@ class Receiver(Common): idA=self.appid+":Initiator", idB=self.appid+":Receiver") + def _wait_for_key(self): + if not self.key: + other_msgs = self._post_pake() + key = self._get_pake(other_msgs) + self.key = key + self.verifier = self.derive_key(self.appid+b":Verifier") + + def get_verifier(self): + self._wait_for_key() + return self.verifier + def get_data(self, outbound_data): assert self.code is not None assert self.channel_id is not None - other_msgs = self._post_pake() - key = self._get_pake(other_msgs) - self.key = key + self._wait_for_key() try: outbound_key = self.derive_key(b"receiver") diff --git a/src/wormhole/scripts/cmd_receive_file.py b/src/wormhole/scripts/cmd_receive_file.py index bf1f74d..d7ae9ef 100644 --- a/src/wormhole/scripts/cmd_receive_file.py +++ b/src/wormhole/scripts/cmd_receive_file.py @@ -1,5 +1,5 @@ from __future__ import print_function -import sys, os, json +import sys, os, json, binascii from wormhole.blocking.transcribe import Receiver, WrongPasswordError from wormhole.blocking.transit import TransitReceiver, TransitError from .progress import start_progress, update_progress, finish_progress @@ -17,6 +17,10 @@ def receive_file(args): args.code_length) r.set_code(code) + if args.verify: + verifier = binascii.hexlify(r.get_verifier()) + print("Verifier %s." % verifier) + mydata = json.dumps({ "transit": { "direct_connection_hints": transit_receiver.get_direct_hints(), @@ -30,6 +34,10 @@ def receive_file(args): return 1 #print("their data: %r" % (data,)) + if "error" in data: + print("ERROR: " + data["error"], file=sys.stderr) + return 1 + file_data = data["file"] filename = os.path.basename(file_data["filename"]) # unicode filesize = file_data["filesize"] diff --git a/src/wormhole/scripts/cmd_receive_text.py b/src/wormhole/scripts/cmd_receive_text.py index d5b0e29..7d3b566 100644 --- a/src/wormhole/scripts/cmd_receive_text.py +++ b/src/wormhole/scripts/cmd_receive_text.py @@ -1,5 +1,5 @@ from __future__ import print_function -import sys, json +import sys, json, binascii from wormhole.blocking.transcribe import Receiver, WrongPasswordError APPID = "lothar.com/wormhole/text-xfer" @@ -12,6 +12,11 @@ def receive_text(args): code = r.input_code("Enter receive-text wormhole code: ", args.code_length) r.set_code(code) + + if args.verify: + verifier = binascii.hexlify(r.get_verifier()) + print("Verifier %s." % verifier) + data = json.dumps({"message": "ok"}).encode("utf-8") try: them_bytes = r.get_data(data) @@ -19,4 +24,7 @@ def receive_text(args): print("ERROR: " + e.explain(), file=sys.stderr) return 1 them_d = json.loads(them_bytes.decode("utf-8")) + if "error" in them_d: + print("ERROR: " + them_d["error"], file=sys.stderr) + return 1 print(them_d["message"]) diff --git a/src/wormhole/scripts/cmd_send_file.py b/src/wormhole/scripts/cmd_send_file.py index bc79527..8384295 100644 --- a/src/wormhole/scripts/cmd_send_file.py +++ b/src/wormhole/scripts/cmd_send_file.py @@ -1,5 +1,5 @@ from __future__ import print_function -import os, sys, json +import os, sys, json, binascii from wormhole.blocking.transcribe import Initiator, WrongPasswordError from wormhole.blocking.transit import TransitSender from .progress import start_progress, update_progress, finish_progress @@ -18,6 +18,20 @@ def send_file(args): print("Wormhole code is '%s'" % code) print() + if args.verify: + verifier = binascii.hexlify(i.get_verifier()) + while True: + ok = raw_input("Verifier %s. ok? (yes/no): " % verifier) + if ok.lower() == "yes": + break + if ok.lower() == "no": + print("verification rejected, abandoning transfer", + file=sys.stderr) + reject_data = json.dumps({"error": "verification rejected", + }).encode("utf-8") + i.get_data(reject_data) + return 1 + filesize = os.stat(filename).st_size data = json.dumps({ "file": { diff --git a/src/wormhole/scripts/cmd_send_text.py b/src/wormhole/scripts/cmd_send_text.py index 9cc8795..4f865ad 100644 --- a/src/wormhole/scripts/cmd_send_text.py +++ b/src/wormhole/scripts/cmd_send_text.py @@ -1,19 +1,34 @@ from __future__ import print_function -import sys, json +import sys, json, binascii from wormhole.blocking.transcribe import Initiator, WrongPasswordError APPID = "lothar.com/wormhole/text-xfer" def send_text(args): # we're sending - message = args.text - data = json.dumps({"message": message, - }).encode("utf-8") i = Initiator(APPID, args.relay_url) code = i.get_code(args.code_length) print("On the other computer, please run: wormhole receive-text") print("Wormhole code is: %s" % code) print("") + + if args.verify: + verifier = binascii.hexlify(i.get_verifier()) + while True: + ok = raw_input("Verifier %s. ok? (yes/no): " % verifier) + if ok.lower() == "yes": + break + if ok.lower() == "no": + print("verification rejected, abandoning transfer", + file=sys.stderr) + reject_data = json.dumps({"error": "verification rejected", + }).encode("utf-8") + i.get_data(reject_data) + return 1 + + message = args.text + data = json.dumps({"message": message, + }).encode("utf-8") try: them_bytes = i.get_data(data) except WrongPasswordError as e: diff --git a/src/wormhole/scripts/runner.py b/src/wormhole/scripts/runner.py index 1ce7f57..0bfa39d 100644 --- a/src/wormhole/scripts/runner.py +++ b/src/wormhole/scripts/runner.py @@ -20,6 +20,8 @@ g.add_argument("--transit-helper", default=public_relay.TRANSIT_RELAY, metavar="tcp:HOST:PORT", help="transit relay to use") g.add_argument("-c", "--code-length", type=int, default=2, metavar="WORDS", help="length of code (in bytes/words)") +g.add_argument("-v", "--verify", action="store_true", + help="display (and wait for acceptance of) verification string") subparsers = parser.add_subparsers(title="subcommands", dest="subcommand")