From 708bcf36d4c007ca85eaa061ad17f84ff6931880 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 25 May 2016 19:36:56 -0700 Subject: [PATCH] INCOMPATIBILITY: send+expect hash of data after xfer This enhances the ACK that wormhole-receive returns when it finishes receiving all the data to be a dictionary. The dict includes the SHA256 hash of everything it received, and the sender checks this for a match before declaring the transfer to be a success. This guards against data being shuffled somehow during transit. --- src/wormhole/cli/cmd_receive.py | 23 +++++++++++++++-------- src/wormhole/cli/cmd_send.py | 24 +++++++++++++++++------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/wormhole/cli/cmd_receive.py b/src/wormhole/cli/cmd_receive.py index dd2d244..7ffa6b6 100644 --- a/src/wormhole/cli/cmd_receive.py +++ b/src/wormhole/cli/cmd_receive.py @@ -1,5 +1,5 @@ from __future__ import print_function -import os, sys, json, binascii, six, tempfile, zipfile +import os, sys, json, binascii, six, tempfile, zipfile, hashlib from tqdm import tqdm from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks, returnValue @@ -164,16 +164,16 @@ class TwistedReceiver: f = self._handle_file(them_d) self._send_permission(w) rp = yield self._establish_transit() - yield self._transfer_data(rp, f) + datahash = yield self._transfer_data(rp, f) self._write_file(f) - yield self._close_transit(rp) + yield self._close_transit(rp, datahash) elif "directory" in them_d: f = self._handle_directory(them_d) self._send_permission(w) rp = yield self._establish_transit() - yield self._transfer_data(rp, f) + datahash = yield self._transfer_data(rp, f) self._write_directory(f) - yield self._close_transit(rp) + yield self._close_transit(rp, datahash) else: self._msg(u"I don't know what they're offering\n") self._msg(u"Offer details: %r" % (them_d,)) @@ -257,9 +257,12 @@ class TwistedReceiver: progress = tqdm(file=self.args.stdout, disable=self.args.hide_progress, unit="B", unit_scale=True, total=self.xfersize) + hasher = hashlib.sha256() with progress: received = yield record_pipe.writeToFile(f, self.xfersize, - progress.update) + progress.update, + hasher.update) + datahash = hasher.digest() # except TransitError if received < self.xfersize: @@ -268,6 +271,7 @@ class TwistedReceiver: self._msg(u"got %d bytes, wanted %d" % (received, self.xfersize)) raise TransferError("Connection dropped before full file received") assert received == self.xfersize + returnValue(datahash) def _write_file(self, f): tmp_name = f.name @@ -290,7 +294,10 @@ class TwistedReceiver: f.close() @inlineCallbacks - def _close_transit(self, record_pipe): + def _close_transit(self, record_pipe, datahash): + datahash_hex = binascii.hexlify(datahash).decode("ascii") + ack = {u"ack": u"ok", u"sha256": datahash_hex} + ack_bytes = json.dumps(ack).encode("utf-8") with self.args.timing.add("send ack"): - yield record_pipe.send_record(b"ok\n") + yield record_pipe.send_record(ack_bytes) yield record_pipe.close() diff --git a/src/wormhole/cli/cmd_send.py b/src/wormhole/cli/cmd_send.py index 58ed8de..72e3a90 100644 --- a/src/wormhole/cli/cmd_send.py +++ b/src/wormhole/cli/cmd_send.py @@ -1,5 +1,5 @@ from __future__ import print_function -import os, sys, json, binascii, six, tempfile, zipfile +import os, sys, json, binascii, six, tempfile, zipfile, hashlib from tqdm import tqdm from twisted.python import log from twisted.protocols import basic @@ -236,11 +236,11 @@ class Sender: raise TransferError("ambiguous response from remote, " "transfer abandoned: %s" % (them_answer,)) - yield self._send_file_twisted() + yield self._send_file() @inlineCallbacks - def _send_file_twisted(self): + def _send_file(self): ts = self._transit_sender self._fd_to_send.seek(0,2) @@ -253,10 +253,12 @@ class Sender: stdout = self._args.stdout print(u"Sending (%s).." % record_pipe.describe(), file=stdout) + hasher = hashlib.sha256() progress = tqdm(file=stdout, disable=self._args.hide_progress, unit="B", unit_scale=True, total=filesize) - def _count(data): + def _count_and_hash(data): + hasher.update(data) progress.update(len(data)) return data fs = basic.FileSender() @@ -264,14 +266,22 @@ class Sender: with self._timing.add("tx file"): with progress: yield fs.beginFileTransfer(self._fd_to_send, record_pipe, - transform=_count) + transform=_count_and_hash) + expected_hash = hasher.digest() + expected_hex = binascii.hexlify(expected_hash).decode("ascii") print(u"File sent.. waiting for confirmation", file=stdout) with self._timing.add("get ack") as t: - ack = yield record_pipe.receive_record() + ack_bytes = yield record_pipe.receive_record() record_pipe.close() - if ack != b"ok\n": + ack = json.loads(ack_bytes.decode("utf-8")) + ok = ack.get(u"ack", u"") + if ok != u"ok": t.detail(ack="failed") raise TransferError("Transfer failed (remote says: %r)" % ack) + if u"sha256" in ack: + if ack[u"sha256"] != expected_hex: + t.detail(datahash="failed") + raise TransferError("Transfer failed (bad remote hash)") print(u"Confirmation received. Transfer complete.", file=stdout) t.detail(ack="ok")