w.verify() now stalls until confirmation message is checked
If it succeeds, you get back the verifier string, which can be compared against the other side. If it fails, the wormhole code didn't match.
This commit is contained in:
parent
8b56892a76
commit
5553729a87
|
@ -224,17 +224,16 @@ class Basic(unittest.TestCase):
|
|||
response(w, type=u"message", phase=u"pake", body=msg2_hex, side=side2)
|
||||
|
||||
# hearing the peer's PAKE (msg2) makes us release the nameplate, send
|
||||
# the confirmation message, delivered the verifier, and sends any
|
||||
# queued phase messages
|
||||
# the confirmation message, and sends any queued phase messages. It
|
||||
# doesn't deliver the verifier because we're still waiting on the
|
||||
# confirmation message.
|
||||
self.assertFalse(w._flag_need_to_see_mailbox_used)
|
||||
self.assertEqual(w._key, key)
|
||||
out = ws.outbound()
|
||||
self.assertEqual(len(out), 2, out)
|
||||
self.check_out(out[0], type=u"release")
|
||||
self.check_out(out[1], type=u"add", phase=u"confirm")
|
||||
verifier = self.successResultOf(v)
|
||||
self.assertEqual(verifier,
|
||||
w.derive_key(u"wormhole:verifier", SecretBox.KEY_SIZE))
|
||||
self.assertNoResult(v)
|
||||
|
||||
# hearing a valid confirmation message doesn't throw an error
|
||||
confkey = w.derive_key(u"wormhole:confirmation", SecretBox.KEY_SIZE)
|
||||
|
@ -244,6 +243,11 @@ class Basic(unittest.TestCase):
|
|||
response(w, type=u"message", phase=u"confirm", body=confirm2_hex,
|
||||
side=side2)
|
||||
|
||||
# and it releases the verifier
|
||||
verifier = self.successResultOf(v)
|
||||
self.assertEqual(verifier,
|
||||
w.derive_key(u"wormhole:verifier", SecretBox.KEY_SIZE))
|
||||
|
||||
# an outbound message can now be sent immediately
|
||||
w.send(b"phase0-outbound")
|
||||
out = ws.outbound()
|
||||
|
@ -506,10 +510,85 @@ class Basic(unittest.TestCase):
|
|||
self.assertEqual(len(pieces), 3) # nameplate plus two words
|
||||
self.assert_(re.search(r'^\d+-\w+-\w+$', code), code)
|
||||
|
||||
# make sure verify() can be called both before and after the verifier is
|
||||
# computed
|
||||
|
||||
def _test_verifier(self, when, order, success):
|
||||
assert when in ("early", "middle", "late")
|
||||
assert order in ("key-then-confirm", "confirm-then-key")
|
||||
assert isinstance(success, bool)
|
||||
#print(when, order, success)
|
||||
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, u"relay_url", reactor, None, timing)
|
||||
w._drop_connection = mock.Mock()
|
||||
w._ws_send_command = mock.Mock()
|
||||
w._mailbox_state = wormhole.OPEN
|
||||
d = None
|
||||
|
||||
if success:
|
||||
w._key = b"key"
|
||||
else:
|
||||
w._key = b"wrongkey"
|
||||
confkey = w._derive_confirmation_key()
|
||||
nonce = os.urandom(wormhole.CONFMSG_NONCE_LENGTH)
|
||||
confmsg = wormhole.make_confmsg(confkey, nonce)
|
||||
w._key = None
|
||||
|
||||
if when == "early":
|
||||
d = w.verify()
|
||||
self.assertNoResult(d)
|
||||
|
||||
if order == "key-then-confirm":
|
||||
w._key = b"key"
|
||||
w._event_established_key()
|
||||
else:
|
||||
w._event_received_confirm(confmsg)
|
||||
|
||||
if when == "middle":
|
||||
d = w.verify()
|
||||
if d:
|
||||
self.assertNoResult(d) # still waiting for other msg
|
||||
|
||||
if order == "confirm-then-key":
|
||||
w._key = b"key"
|
||||
w._event_established_key()
|
||||
else:
|
||||
w._event_received_confirm(confmsg)
|
||||
|
||||
if when == "late":
|
||||
d = w.verify()
|
||||
if success:
|
||||
self.successResultOf(d)
|
||||
else:
|
||||
self.assertFailure(d, wormhole.WrongPasswordError)
|
||||
self.flushLoggedErrors(WrongPasswordError)
|
||||
|
||||
def test_verifier(self):
|
||||
# make sure verify() can be called both before and after the verifier
|
||||
# is computed
|
||||
pass
|
||||
for when in ("early", "middle", "late"):
|
||||
for order in ("key-then-confirm", "confirm-then-key"):
|
||||
for success in (False, True):
|
||||
self._test_verifier(when, order, success)
|
||||
|
||||
def test_verifier_4(self):
|
||||
# ask early, key-then-confirm, success
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, u"relay_url", reactor, None, timing)
|
||||
w._drop_connection = mock.Mock()
|
||||
w._ws_send_command = mock.Mock()
|
||||
w._mailbox_state = wormhole.OPEN
|
||||
d = w.verify()
|
||||
self.assertNoResult(d)
|
||||
|
||||
w._key = b"key"
|
||||
w._event_established_key()
|
||||
self.assertNoResult(d) # still waiting for confirmation message
|
||||
confkey = w._derive_confirmation_key()
|
||||
nonce = os.urandom(wormhole.CONFMSG_NONCE_LENGTH)
|
||||
confmsg = wormhole.make_confmsg(confkey, nonce)
|
||||
w._event_received_confirm(confmsg)
|
||||
self.successResultOf(d)
|
||||
|
||||
|
||||
def test_api_errors(self):
|
||||
# doing things you're not supposed to do
|
||||
|
@ -578,8 +657,7 @@ class Basic(unittest.TestCase):
|
|||
msg2_hex = hexlify(msg2).decode("ascii")
|
||||
response(w, type=u"message", phase=u"pake", body=msg2_hex, side=u"s2")
|
||||
self.assertNoResult(d1)
|
||||
self.successResultOf(d2) # early verify is unaffected
|
||||
# TODO: change verify() to wait for "confirm"
|
||||
self.assertNoResult(d2) # verify() waits for confirmation
|
||||
|
||||
# sending a random confirm message will cause a confirmation error
|
||||
confkey = w.derive_key(u"WRONG", SecretBox.KEY_SIZE)
|
||||
|
@ -590,6 +668,7 @@ class Basic(unittest.TestCase):
|
|||
side=u"s2")
|
||||
|
||||
self.failureResultOf(d1, WrongPasswordError)
|
||||
self.failureResultOf(d2, WrongPasswordError)
|
||||
|
||||
# once the error is signalled, all API calls should fail
|
||||
self.assertRaises(WrongPasswordError, w.send, u"foo")
|
||||
|
|
|
@ -5,7 +5,7 @@ from binascii import hexlify, unhexlify
|
|||
from twisted.internet import defer, endpoints, error
|
||||
from twisted.internet.threads import deferToThread, blockingCallFromThread
|
||||
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||
from twisted.python import log
|
||||
from twisted.python import log, failure
|
||||
from autobahn.twisted import websocket
|
||||
from nacl.secret import SecretBox
|
||||
from nacl.exceptions import CryptoError
|
||||
|
@ -237,15 +237,19 @@ class _Wormhole:
|
|||
self._flag_need_to_build_msg1 = True
|
||||
self._flag_need_to_send_PAKE = True
|
||||
self._key = None
|
||||
|
||||
self._confirmation_message = None
|
||||
self._confirmation_checked = False
|
||||
self._get_verifier_called = False
|
||||
self._verifier = None # bytes
|
||||
self._verify_result = None # bytes or a Failure
|
||||
self._verifier_waiter = None
|
||||
|
||||
self._close_called = False # the close() API has been called
|
||||
self._closing = False # we've started shutdown
|
||||
self._disconnect_waiter = defer.Deferred()
|
||||
self._error = None
|
||||
|
||||
self._get_verifier_called = False
|
||||
self._verifier = None
|
||||
self._verifier_waiter = None
|
||||
|
||||
self._next_send_phase = 0
|
||||
# send() queues plaintext here, waiting for a connection and the key
|
||||
self._plaintext_to_send = [] # (phase, plaintext)
|
||||
|
@ -549,38 +553,55 @@ class _Wormhole:
|
|||
verifier = self._derive_key(b"wormhole:verifier")
|
||||
self._event_computed_verifier(verifier)
|
||||
|
||||
self._maybe_check_confirmation()
|
||||
self._maybe_send_phase_messages()
|
||||
|
||||
def _API_verify(self):
|
||||
# TODO: rename "verify()", make it stall until confirm received. If
|
||||
# you want to discover WrongPasswordError before doing send(), call
|
||||
# verify() first. If you also want to deny a successful MitM (and
|
||||
# have some other way to check a long verifier), use the return value
|
||||
# of verify().
|
||||
if self._error: return defer.fail(self._error)
|
||||
if self._get_verifier_called: raise UsageError
|
||||
self._get_verifier_called = True
|
||||
if self._verifier:
|
||||
return defer.succeed(self._verifier)
|
||||
# TODO: maybe have this wait on _event_received_confirm too
|
||||
if self._verify_result:
|
||||
return defer.succeed(self._verify_result) # bytes or Failure
|
||||
self._verifier_waiter = defer.Deferred()
|
||||
return self._verifier_waiter
|
||||
|
||||
def _event_computed_verifier(self, verifier):
|
||||
self._verifier = verifier
|
||||
if self._verifier_waiter:
|
||||
self._verifier_waiter.callback(verifier)
|
||||
self._maybe_notify_verify()
|
||||
|
||||
def _maybe_notify_verify(self):
|
||||
if not (self._verifier and self._confirmation_checked):
|
||||
return
|
||||
if self._error:
|
||||
self._verify_result = failure.Failure(self._error)
|
||||
else:
|
||||
self._verify_result = self._verifier
|
||||
if self._verifier_waiter and not self._verifier_waiter.called:
|
||||
self._verifier_waiter.callback(self._verify_result)
|
||||
|
||||
def _event_received_confirm(self, body):
|
||||
# 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.
|
||||
# We ought to have the master key by now, because sensible peers
|
||||
# should always send "pake" before sending "confirm". It might be
|
||||
# nice to relax this requirement, which means storing the received
|
||||
# confirmation message, and having _event_established_key call
|
||||
# _check_confirmation()
|
||||
self._confirmation_message = body
|
||||
self._maybe_check_confirmation()
|
||||
|
||||
def _maybe_check_confirmation(self):
|
||||
if not (self._key and self._confirmation_message):
|
||||
return
|
||||
if self._confirmation_checked:
|
||||
return
|
||||
confkey = self._derive_confirmation_key()
|
||||
body = self._confirmation_message
|
||||
nonce = body[:CONFMSG_NONCE_LENGTH]
|
||||
if body != make_confmsg(confkey, nonce):
|
||||
# this makes all API calls fail
|
||||
if self.DEBUG: print("CONFIRM FAILED")
|
||||
return self._signal_error(WrongPasswordError(), u"scary")
|
||||
self._signal_error(WrongPasswordError(), u"scary")
|
||||
self._confirmation_checked = True
|
||||
self._maybe_notify_verify()
|
||||
|
||||
|
||||
def _API_send(self, outbound_data):
|
||||
|
|
Loading…
Reference in New Issue
Block a user