From b70c2f8868d8b50f1d4e997e78f9be5225ed93a5 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 25 Apr 2016 18:45:55 -0700 Subject: [PATCH] Make get_verifier() wait for _confirm to arrive This improves the error behavior when --verify is used but there's a WrongPasswordError: the mismatch is detected before the verifiers are displayed or confirmation is requested. It requires that the far end sends a "_confirm" message, which was introduced in release 0.6.0. Use with older versions (if it doesn't break for other reasons) will cause a hang. This patch also deletes test_twisted.Basic.test_verifier_mismatch, since both sides now detect this on their own. It changes test_wrong_password() too, since we might now notice the error during send_data (previously we'd only see it in get_data). --- src/wormhole/test/test_twisted.py | 26 ++++++++++---------------- src/wormhole/twisted/transcribe.py | 14 +++++++++++++- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/wormhole/test/test_twisted.py b/src/wormhole/test/test_twisted.py index 61b4dd5..1c186da 100644 --- a/src/wormhole/test/test_twisted.py +++ b/src/wormhole/test/test_twisted.py @@ -109,11 +109,20 @@ class Basic(ServerBase, unittest.TestCase): # * w2 will send w2.PAKE, wait for (and get) w1.PAKE, compute a key, # send w2.CONFIRM, then wait for w1.DATA. # * w1 will get w2.PAKE, compute a key, send w1.CONFIRM. + # * w1 might also get w2.CONFIRM, and may notice the error before it + # sends w1.CONFIRM, in which case the wait=True will signal an + # error inside _get_master_key() (inside send_data), and d1 will + # errback. + # * but w1 might not see w2.CONFIRM yet, in which case it won't + # errback until we do w1.get_data() # * w2 gets w1.CONFIRM, notices the error, records it. # * w2 (waiting for w1.DATA) wakes up, sees the error, throws # * meanwhile w1 finishes sending its data. w2.CONFIRM may or may not # have arrived by then - yield d1 + try: + yield d1 + except WrongPasswordError: + pass # When we ask w1 to get_data(), one of two things might happen: # * if w2.CONFIRM arrived already, it will have recorded the error. @@ -167,21 +176,6 @@ class Basic(ServerBase, unittest.TestCase): self.assertEqual(dataY, b"data1") yield self.doBoth(w1.close(), w2.close()) - @inlineCallbacks - def test_verifier_mismatch(self): - w1 = Wormhole(APPID, self.relayurl) - w2 = Wormhole(APPID, self.relayurl) - # we must disable confirmation messages, else the wormholes will - # figure out the mismatch by themselves and throw WrongPasswordError. - w1._send_confirm = w2._send_confirm = False - code = yield w1.get_code() - w2.set_code(code+"not") - res = yield self.doBoth(w1.get_verifier(), w2.get_verifier()) - v1, v2 = res - self.failUnlessEqual(type(v1), type(b"")) - self.failIfEqual(v1, v2) - yield self.doBoth(w1.close(), w2.close()) - @inlineCallbacks def test_errors(self): w1 = Wormhole(APPID, self.relayurl) diff --git a/src/wormhole/twisted/transcribe.py b/src/wormhole/twisted/transcribe.py index 8219834..b026e2b 100644 --- a/src/wormhole/twisted/transcribe.py +++ b/src/wormhole/twisted/transcribe.py @@ -350,6 +350,15 @@ class Wormhole: if self._closed: raise UsageError if self._code is None: raise UsageError yield self._get_master_key() + # If the caller cares about the verifier, then they'll probably also + # willing to wait a moment to see the _confirm message. Each side + # sends this as soon as it sees the other's PAKE message. So the + # sender should see this hot on the heels of the inbound PAKE message + # (a moment after _get_master_key() returns). The receiver will see + # this a round-trip after they send their PAKE (because the sender is + # using wait=True inside _get_master_key, below: otherwise the sender + # might go do some blocking call). + yield self._msg_get(u"_confirm") returnValue(self._verifier) @inlineCallbacks @@ -369,7 +378,7 @@ class Wormhole: confkey = self.derive_key(u"wormhole:confirmation") nonce = os.urandom(CONFMSG_NONCE_LENGTH) confmsg = make_confmsg(confkey, nonce) - yield self._msg_send(u"_confirm", confmsg) + yield self._msg_send(u"_confirm", confmsg, wait=True) @inlineCallbacks def _msg_send(self, phase, body, wait=False): @@ -396,6 +405,9 @@ class Wormhole: return self._signal_error(err) self._received_messages[phase] = body if phase == u"_confirm": + # TODO: we might not have a master key yet, if the caller wasn't + # waiting in _get_master_key() when a back-to-back pake+_confirm + # message pair arrived. confkey = self.derive_key(u"wormhole:confirmation") nonce = body[:CONFMSG_NONCE_LENGTH] if body != make_confmsg(confkey, nonce):