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).
This commit is contained in:
Brian Warner 2016-04-25 18:45:55 -07:00
parent c017de5e4b
commit b70c2f8868
2 changed files with 23 additions and 17 deletions

View File

@ -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
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)

View File

@ -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):