INCOMPATIBILITY: change "confirm" message to include version dict

This gives the two Wormholes a way to signal capabilities to each other,
before the applications start sending their own messages.
This commit is contained in:
Brian Warner 2016-05-25 18:27:37 -07:00
parent e1c488247f
commit 7f43561a50
2 changed files with 56 additions and 21 deletions

View File

@ -236,10 +236,10 @@ class Basic(unittest.TestCase):
self.assertNoResult(v)
# hearing a valid confirmation message doesn't throw an error
confkey = w.derive_key(u"wormhole:confirmation", SecretBox.KEY_SIZE)
nonce = os.urandom(wormhole.CONFMSG_NONCE_LENGTH)
confirm2 = wormhole.make_confmsg(confkey, nonce)
confirm2_hex = hexlify(confirm2).decode("ascii")
plaintext = json.dumps({}).encode("utf-8")
data_key = w._derive_phase_key(side2, u"confirm")
confmsg = w._encrypt_data(data_key, plaintext)
confirm2_hex = hexlify(confmsg).decode("ascii")
response(w, type=u"message", phase=u"confirm", body=confirm2_hex,
side=side2)
@ -524,15 +524,16 @@ class Basic(unittest.TestCase):
w._drop_connection = mock.Mock()
w._ws_send_command = mock.Mock()
w._mailbox_state = wormhole.OPEN
side2 = u"side2"
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)
plaintext = json.dumps({}).encode("utf-8")
data_key = w._derive_phase_key(side2, u"confirm")
confmsg = w._encrypt_data(data_key, plaintext)
w._key = None
if when == "early":
@ -543,7 +544,7 @@ class Basic(unittest.TestCase):
w._key = b"key"
w._event_established_key()
else:
w._event_received_confirm(confmsg)
w._event_received_confirm(side2, confmsg)
if when == "middle":
d = w.verify()
@ -554,7 +555,7 @@ class Basic(unittest.TestCase):
w._key = b"key"
w._event_established_key()
else:
w._event_received_confirm(confmsg)
w._event_received_confirm(side2, confmsg)
if when == "late":
d = w.verify()
@ -824,6 +825,22 @@ class Wormholes(ServerBase, unittest.TestCase):
yield w1.close()
yield w2.close()
@inlineCallbacks
def test_versions(self):
# there's no API for this yet, but make sure the internals work
w1 = wormhole.wormhole(APPID, self.relayurl, reactor)
w1._my_versions = {u"w1": 123}
w2 = wormhole.wormhole(APPID, self.relayurl, reactor)
w2._my_versions = {u"w2": 456}
code = yield w1.get_code()
w2.set_code(code)
yield w1.verify()
self.assertEqual(w1._their_versions, {u"w2": 456})
yield w2.verify()
self.assertEqual(w2._their_versions, {u"w1": 123})
yield w1.close()
yield w2.close()
class Errors(ServerBase, unittest.TestCase):
@inlineCallbacks
def test_codes_1(self):

View File

@ -245,6 +245,9 @@ class _Wormhole:
self._verify_result = None # bytes or a Failure
self._verifier_waiter = None
self._my_versions = {} # sent
self._their_versions = {} # received
self._close_called = False # the close() API has been called
self._closing = False # we've started shutdown
self._disconnect_waiter = defer.Deferred()
@ -545,10 +548,7 @@ class _Wormhole:
self._timing.add("key established")
# both sides send different (random) confirmation messages
confkey = self._derive_confirmation_key()
nonce = os.urandom(CONFMSG_NONCE_LENGTH)
confmsg = make_confmsg(confkey, nonce)
self._msg_send(u"confirm", confmsg)
self._send_confirmation_message()
verifier = self._derive_key(b"wormhole:verifier")
self._event_computed_verifier(verifier)
@ -556,6 +556,16 @@ class _Wormhole:
self._maybe_check_confirmation()
self._maybe_send_phase_messages()
def _send_confirmation_message(self):
# this is encrypted like a normal phase message, and includes a
# dictionary of version flags to let the other Wormhole know what
# we're capable of (for future expansion)
plaintext = json.dumps(self._my_versions).encode("utf-8")
phase = u"confirm"
data_key = self._derive_phase_key(self._side, phase)
encrypted = self._encrypt_data(data_key, plaintext)
self._msg_send(phase, encrypted)
def _API_verify(self):
if self._error: return defer.fail(self._error)
if self._get_verifier_called: raise UsageError
@ -579,13 +589,13 @@ class _Wormhole:
if self._verifier_waiter and not self._verifier_waiter.called:
self._verifier_waiter.callback(self._verify_result)
def _event_received_confirm(self, body):
def _event_received_confirm(self, side, body):
# 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._confirmation_message = (side, body)
self._maybe_check_confirmation()
def _maybe_check_confirmation(self):
@ -593,16 +603,24 @@ class _Wormhole:
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):
self._confirmation_checked = True
side, body = self._confirmation_message
data_key = self._derive_phase_key(side, u"confirm")
try:
plaintext = self._decrypt_data(data_key, body)
except CryptoError:
# this makes all API calls fail
if self.DEBUG: print("CONFIRM FAILED")
self._signal_error(WrongPasswordError(), u"scary")
self._confirmation_checked = True
return
msg = json.loads(plaintext.decode("utf-8"))
self._version_received(msg)
self._maybe_notify_verify()
def _version_received(self, msg):
self._their_versions = msg
def _API_send(self, outbound_data):
if self._error: raise self._error
@ -703,7 +721,7 @@ class _Wormhole:
if phase == u"pake":
return self._event_received_pake(body)
if phase == u"confirm":
return self._event_received_confirm(body)
return self._event_received_confirm(side, body)
if re.search(r'^\d+$', phase):
return self._event_received_phase_message(side, phase, body)
# ignore unrecognized phases, for forwards-compatibility