merge test_wormhole_new into test_wormhole
This commit is contained in:
parent
8d47194612
commit
8882e6f64e
|
@ -1,20 +1,14 @@
|
|||
from __future__ import print_function, unicode_literals
|
||||
import os, json, re, gc, io
|
||||
from binascii import hexlify, unhexlify
|
||||
import json, io, re
|
||||
import mock
|
||||
from twisted.trial import unittest
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.defer import Deferred, gatherResults, inlineCallbacks
|
||||
from twisted.internet.defer import gatherResults, inlineCallbacks
|
||||
from .common import ServerBase
|
||||
from .. import wormhole, _rendezvous
|
||||
from ..errors import (WrongPasswordError, WelcomeError, InternalError,
|
||||
from ..errors import (WrongPasswordError,
|
||||
KeyFormatError, WormholeClosed, LonelyError,
|
||||
NoKeyError, OnlyOneCodeError)
|
||||
from spake2 import SPAKE2_Symmetric
|
||||
from ..timing import DebugTiming
|
||||
from ..util import (bytes_to_dict, dict_to_bytes,
|
||||
hexstr_to_bytes, bytes_to_hexstr)
|
||||
from nacl.secret import SecretBox
|
||||
|
||||
APPID = "appid"
|
||||
|
||||
|
@ -53,587 +47,6 @@ class Welcome(unittest.TestCase):
|
|||
("Server (at relay_url) says:\n message of\n the day\n"
|
||||
"Server (at relay_url) says:\n second message\n"))
|
||||
|
||||
class Basic(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
# flush out any errorful Deferreds left dangling in cycles
|
||||
gc.collect()
|
||||
|
||||
def check_out(self, out, **kwargs):
|
||||
# Assert that each kwarg is present in the 'out' dict. Ignore other
|
||||
# keys ('msgid' in particular)
|
||||
for key, value in kwargs.items():
|
||||
self.assertIn(key, out)
|
||||
self.assertEqual(out[key], value, (out, key, value))
|
||||
|
||||
def check_outbound(self, ws, types):
|
||||
out = ws.outbound()
|
||||
self.assertEqual(len(out), len(types), (out, types))
|
||||
for i,t in enumerate(types):
|
||||
self.assertEqual(out[i]["type"], t, (i,t,out))
|
||||
return out
|
||||
|
||||
def make_pake(self, code, side, msg1):
|
||||
sp2 = SPAKE2_Symmetric(wormhole.to_bytes(code),
|
||||
idSymmetric=wormhole.to_bytes(APPID))
|
||||
msg2 = sp2.start()
|
||||
key = sp2.finish(msg1)
|
||||
return key, msg2
|
||||
|
||||
def test_create(self):
|
||||
wormhole._Wormhole(APPID, "relay_url", reactor, None, None, None)
|
||||
|
||||
def test_basic(self):
|
||||
# We don't call w._start(), so this doesn't create a WebSocket
|
||||
# connection. We provide a mock connection instead. If we wanted to
|
||||
# exercise _connect, we'd mock out WSFactory.
|
||||
# w._connect = lambda self: None
|
||||
# w._event_connected(mock_ws)
|
||||
# w._event_ws_opened()
|
||||
# w._ws_dispatch_response(payload)
|
||||
|
||||
timing = DebugTiming()
|
||||
with mock.patch("wormhole.wormhole._WelcomeHandler") as wh_c:
|
||||
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing,
|
||||
None)
|
||||
wh = wh_c.return_value
|
||||
self.assertEqual(w._ws_url, "relay_url")
|
||||
self.assertTrue(w._flag_need_nameplate)
|
||||
self.assertTrue(w._flag_need_to_build_msg1)
|
||||
self.assertTrue(w._flag_need_to_send_PAKE)
|
||||
|
||||
v = w.verify()
|
||||
|
||||
w._drop_connection = mock.Mock()
|
||||
ws = MockWebSocket()
|
||||
w._event_connected(ws)
|
||||
out = ws.outbound()
|
||||
self.assertEqual(len(out), 0)
|
||||
|
||||
w._event_ws_opened(None)
|
||||
out = ws.outbound()
|
||||
self.assertEqual(len(out), 1)
|
||||
self.check_out(out[0], type="bind", appid=APPID, side=w._side)
|
||||
self.assertIn("id", out[0])
|
||||
|
||||
# WelcomeHandler should get called upon 'welcome' response. Its full
|
||||
# behavior is exercised in 'Welcome' above.
|
||||
WELCOME = {"foo": "bar"}
|
||||
response(w, type="welcome", welcome=WELCOME)
|
||||
self.assertEqual(wh.mock_calls, [mock.call.handle_welcome(WELCOME)])
|
||||
|
||||
# because we're connected, setting the code also claims the mailbox
|
||||
CODE = "123-foo-bar"
|
||||
w.set_code(CODE)
|
||||
self.assertFalse(w._flag_need_to_build_msg1)
|
||||
out = ws.outbound()
|
||||
self.assertEqual(len(out), 1)
|
||||
self.check_out(out[0], type="claim", nameplate="123")
|
||||
|
||||
# the server reveals the linked mailbox
|
||||
response(w, type="claimed", mailbox="mb456")
|
||||
|
||||
# that triggers event_learned_mailbox, which should send open() and
|
||||
# PAKE
|
||||
self.assertEqual(w._mailbox_state, wormhole.OPEN)
|
||||
out = ws.outbound()
|
||||
self.assertEqual(len(out), 2)
|
||||
self.check_out(out[0], type="open", mailbox="mb456")
|
||||
self.check_out(out[1], type="add", phase="pake")
|
||||
self.assertNoResult(v)
|
||||
|
||||
# server echoes back all "add" messages
|
||||
response(w, type="message", phase="pake", body=out[1]["body"],
|
||||
side=w._side)
|
||||
self.assertNoResult(v)
|
||||
|
||||
# extract our outbound PAKE message
|
||||
body = bytes_to_dict(hexstr_to_bytes(out[1]["body"]))
|
||||
msg1 = hexstr_to_bytes(body["pake_v1"])
|
||||
|
||||
# next we build the simulated peer's PAKE operation
|
||||
side2 = w._side + "other"
|
||||
key, msg2 = self.make_pake(CODE, side2, msg1)
|
||||
payload = {"pake_v1": bytes_to_hexstr(msg2)}
|
||||
body_hex = bytes_to_hexstr(dict_to_bytes(payload))
|
||||
response(w, type="message", phase="pake", body=body_hex, side=side2)
|
||||
|
||||
# hearing the peer's PAKE (msg2) makes us release the nameplate, send
|
||||
# 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="release")
|
||||
self.check_out(out[1], type="add", phase="version")
|
||||
self.assertNoResult(v)
|
||||
|
||||
# hearing a valid confirmation message doesn't throw an error
|
||||
plaintext = json.dumps({}).encode("utf-8")
|
||||
data_key = w._derive_phase_key(side2, "version")
|
||||
confmsg = w._encrypt_data(data_key, plaintext)
|
||||
version2_hex = hexlify(confmsg).decode("ascii")
|
||||
response(w, type="message", phase="version", body=version2_hex,
|
||||
side=side2)
|
||||
|
||||
# and it releases the verifier
|
||||
verifier = self.successResultOf(v)
|
||||
self.assertEqual(verifier,
|
||||
w.derive_key("wormhole:verifier", SecretBox.KEY_SIZE))
|
||||
|
||||
# an outbound message can now be sent immediately
|
||||
w.send(b"phase0-outbound")
|
||||
out = ws.outbound()
|
||||
self.assertEqual(len(out), 1)
|
||||
self.check_out(out[0], type="add", phase="0")
|
||||
# decrypt+check the outbound message
|
||||
p0_outbound = unhexlify(out[0]["body"].encode("ascii"))
|
||||
msgkey0 = w._derive_phase_key(w._side, "0")
|
||||
p0_plaintext = w._decrypt_data(msgkey0, p0_outbound)
|
||||
self.assertEqual(p0_plaintext, b"phase0-outbound")
|
||||
|
||||
# get() waits for the inbound message to arrive
|
||||
md = w.get()
|
||||
self.assertNoResult(md)
|
||||
self.assertIn("0", w._receive_waiters)
|
||||
self.assertNotIn("0", w._received_messages)
|
||||
msgkey1 = w._derive_phase_key(side2, "0")
|
||||
p0_inbound = w._encrypt_data(msgkey1, b"phase0-inbound")
|
||||
p0_inbound_hex = hexlify(p0_inbound).decode("ascii")
|
||||
response(w, type="message", phase="0", body=p0_inbound_hex,
|
||||
side=side2)
|
||||
p0_in = self.successResultOf(md)
|
||||
self.assertEqual(p0_in, b"phase0-inbound")
|
||||
self.assertNotIn("0", w._receive_waiters)
|
||||
self.assertIn("0", w._received_messages)
|
||||
|
||||
# receiving an inbound message will queue it until get() is called
|
||||
msgkey2 = w._derive_phase_key(side2, "1")
|
||||
p1_inbound = w._encrypt_data(msgkey2, b"phase1-inbound")
|
||||
p1_inbound_hex = hexlify(p1_inbound).decode("ascii")
|
||||
response(w, type="message", phase="1", body=p1_inbound_hex,
|
||||
side=side2)
|
||||
self.assertIn("1", w._received_messages)
|
||||
self.assertNotIn("1", w._receive_waiters)
|
||||
p1_in = self.successResultOf(w.get())
|
||||
self.assertEqual(p1_in, b"phase1-inbound")
|
||||
self.assertIn("1", w._received_messages)
|
||||
self.assertNotIn("1", w._receive_waiters)
|
||||
|
||||
d = w.close()
|
||||
self.assertNoResult(d)
|
||||
out = ws.outbound()
|
||||
self.assertEqual(len(out), 1)
|
||||
self.check_out(out[0], type="close", mood="happy")
|
||||
self.assertEqual(w._drop_connection.mock_calls, [])
|
||||
|
||||
response(w, type="released")
|
||||
self.assertEqual(w._drop_connection.mock_calls, [])
|
||||
response(w, type="closed")
|
||||
self.assertEqual(w._drop_connection.mock_calls, [mock.call()])
|
||||
w._ws_closed(True, None, None)
|
||||
self.assertEqual(self.successResultOf(d), None)
|
||||
|
||||
def test_close_wait_0(self):
|
||||
# Close before the connection is established. The connection still
|
||||
# gets established, but it is then torn down before sending anything.
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
|
||||
w._drop_connection = mock.Mock()
|
||||
|
||||
d = w.close()
|
||||
self.assertNoResult(d)
|
||||
|
||||
ws = MockWebSocket()
|
||||
w._event_connected(ws)
|
||||
w._event_ws_opened(None)
|
||||
self.assertEqual(w._drop_connection.mock_calls, [mock.call()])
|
||||
self.assertNoResult(d)
|
||||
|
||||
w._ws_closed(True, None, None)
|
||||
self.successResultOf(d)
|
||||
|
||||
def test_close_wait_1(self):
|
||||
# close before even claiming the nameplate
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
|
||||
w._drop_connection = mock.Mock()
|
||||
ws = MockWebSocket()
|
||||
w._event_connected(ws)
|
||||
w._event_ws_opened(None)
|
||||
|
||||
d = w.close()
|
||||
self.check_outbound(ws, ["bind"])
|
||||
self.assertNoResult(d)
|
||||
self.assertEqual(w._drop_connection.mock_calls, [mock.call()])
|
||||
self.assertNoResult(d)
|
||||
|
||||
w._ws_closed(True, None, None)
|
||||
self.successResultOf(d)
|
||||
|
||||
def test_close_wait_2(self):
|
||||
# Close after claiming the nameplate, but before opening the mailbox.
|
||||
# The 'claimed' response arrives before we close.
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
|
||||
w._drop_connection = mock.Mock()
|
||||
ws = MockWebSocket()
|
||||
w._event_connected(ws)
|
||||
w._event_ws_opened(None)
|
||||
CODE = "123-foo-bar"
|
||||
w.set_code(CODE)
|
||||
self.check_outbound(ws, ["bind", "claim"])
|
||||
|
||||
response(w, type="claimed", mailbox="mb123")
|
||||
|
||||
d = w.close()
|
||||
self.check_outbound(ws, ["open", "add", "release", "close"])
|
||||
self.assertNoResult(d)
|
||||
self.assertEqual(w._drop_connection.mock_calls, [])
|
||||
|
||||
response(w, type="released")
|
||||
self.assertNoResult(d)
|
||||
self.assertEqual(w._drop_connection.mock_calls, [])
|
||||
|
||||
response(w, type="closed")
|
||||
self.assertEqual(w._drop_connection.mock_calls, [mock.call()])
|
||||
self.assertNoResult(d)
|
||||
|
||||
w._ws_closed(True, None, None)
|
||||
self.successResultOf(d)
|
||||
|
||||
def test_close_wait_3(self):
|
||||
# close after claiming the nameplate, but before opening the mailbox
|
||||
# The 'claimed' response arrives after we start to close.
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
|
||||
w._drop_connection = mock.Mock()
|
||||
ws = MockWebSocket()
|
||||
w._event_connected(ws)
|
||||
w._event_ws_opened(None)
|
||||
CODE = "123-foo-bar"
|
||||
w.set_code(CODE)
|
||||
self.check_outbound(ws, ["bind", "claim"])
|
||||
|
||||
d = w.close()
|
||||
response(w, type="claimed", mailbox="mb123")
|
||||
self.check_outbound(ws, ["release"])
|
||||
self.assertNoResult(d)
|
||||
self.assertEqual(w._drop_connection.mock_calls, [])
|
||||
|
||||
response(w, type="released")
|
||||
self.assertEqual(w._drop_connection.mock_calls, [mock.call()])
|
||||
self.assertNoResult(d)
|
||||
|
||||
w._ws_closed(True, None, None)
|
||||
self.successResultOf(d)
|
||||
|
||||
def test_close_wait_4(self):
|
||||
# close after both claiming the nameplate and opening the mailbox
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
|
||||
w._drop_connection = mock.Mock()
|
||||
ws = MockWebSocket()
|
||||
w._event_connected(ws)
|
||||
w._event_ws_opened(None)
|
||||
CODE = "123-foo-bar"
|
||||
w.set_code(CODE)
|
||||
response(w, type="claimed", mailbox="mb456")
|
||||
self.check_outbound(ws, ["bind", "claim", "open", "add"])
|
||||
|
||||
d = w.close()
|
||||
self.check_outbound(ws, ["release", "close"])
|
||||
self.assertNoResult(d)
|
||||
self.assertEqual(w._drop_connection.mock_calls, [])
|
||||
|
||||
response(w, type="released")
|
||||
self.assertNoResult(d)
|
||||
self.assertEqual(w._drop_connection.mock_calls, [])
|
||||
|
||||
response(w, type="closed")
|
||||
self.assertNoResult(d)
|
||||
self.assertEqual(w._drop_connection.mock_calls, [mock.call()])
|
||||
|
||||
w._ws_closed(True, None, None)
|
||||
self.successResultOf(d)
|
||||
|
||||
def test_close_wait_5(self):
|
||||
# close after claiming the nameplate, opening the mailbox, then
|
||||
# releasing the nameplate
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
|
||||
w._drop_connection = mock.Mock()
|
||||
ws = MockWebSocket()
|
||||
w._event_connected(ws)
|
||||
w._event_ws_opened(None)
|
||||
CODE = "123-foo-bar"
|
||||
w.set_code(CODE)
|
||||
response(w, type="claimed", mailbox="mb456")
|
||||
|
||||
w._key = b""
|
||||
msgkey = w._derive_phase_key("side2", "misc")
|
||||
p1_inbound = w._encrypt_data(msgkey, b"")
|
||||
p1_inbound_hex = hexlify(p1_inbound).decode("ascii")
|
||||
response(w, type="message", phase="misc", side="side2",
|
||||
body=p1_inbound_hex)
|
||||
self.check_outbound(ws, ["bind", "claim", "open", "add",
|
||||
"release"])
|
||||
|
||||
d = w.close()
|
||||
self.check_outbound(ws, ["close"])
|
||||
self.assertNoResult(d)
|
||||
self.assertEqual(w._drop_connection.mock_calls, [])
|
||||
|
||||
response(w, type="released")
|
||||
self.assertNoResult(d)
|
||||
self.assertEqual(w._drop_connection.mock_calls, [])
|
||||
|
||||
response(w, type="closed")
|
||||
self.assertNoResult(d)
|
||||
self.assertEqual(w._drop_connection.mock_calls, [mock.call()])
|
||||
|
||||
w._ws_closed(True, None, None)
|
||||
self.successResultOf(d)
|
||||
|
||||
def test_close_errbacks(self):
|
||||
# make sure the Deferreds returned by verify() and get() are properly
|
||||
# errbacked upon close
|
||||
pass
|
||||
|
||||
def test_get_code_mock(self):
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
|
||||
ws = MockWebSocket() # TODO: mock w._ws_send_command instead
|
||||
w._event_connected(ws)
|
||||
w._event_ws_opened(None)
|
||||
self.check_outbound(ws, ["bind"])
|
||||
|
||||
gc_c = mock.Mock()
|
||||
gc = gc_c.return_value = mock.Mock()
|
||||
gc_d = gc.go.return_value = Deferred()
|
||||
with mock.patch("wormhole.wormhole._GetCode", gc_c):
|
||||
d = w.get_code()
|
||||
self.assertNoResult(d)
|
||||
|
||||
gc_d.callback("123-foo-bar")
|
||||
code = self.successResultOf(d)
|
||||
self.assertEqual(code, "123-foo-bar")
|
||||
|
||||
def test_get_code_real(self):
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
|
||||
ws = MockWebSocket()
|
||||
w._event_connected(ws)
|
||||
w._event_ws_opened(None)
|
||||
self.check_outbound(ws, ["bind"])
|
||||
|
||||
d = w.get_code()
|
||||
|
||||
out = ws.outbound()
|
||||
self.assertEqual(len(out), 1)
|
||||
self.check_out(out[0], type="allocate")
|
||||
# TODO: nameplate attributes go here
|
||||
self.assertNoResult(d)
|
||||
|
||||
response(w, type="allocated", nameplate="123")
|
||||
code = self.successResultOf(d)
|
||||
self.assertIsInstance(code, type(""))
|
||||
self.assert_(code.startswith("123-"))
|
||||
pieces = code.split("-")
|
||||
self.assertEqual(len(pieces), 3) # nameplate plus two words
|
||||
self.assert_(re.search(r'^\d+-\w+-\w+$', code), code)
|
||||
|
||||
def _test_establish_key_hook(self, established, before):
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
|
||||
|
||||
if before:
|
||||
d = w.establish_key()
|
||||
|
||||
if established is True:
|
||||
w._key = b"key"
|
||||
elif established is False:
|
||||
w._key = None
|
||||
else:
|
||||
w._key = b"key"
|
||||
w._error = WelcomeError()
|
||||
|
||||
if not before:
|
||||
d = w.establish_key()
|
||||
else:
|
||||
w._maybe_notify_key()
|
||||
|
||||
if w._key is not None and established is True:
|
||||
self.successResultOf(d)
|
||||
elif established is False:
|
||||
self.assertNot(d.called)
|
||||
else:
|
||||
self.failureResultOf(d)
|
||||
|
||||
def test_establish_key_hook(self):
|
||||
for established in (True, False, "error"):
|
||||
for before in (True, False):
|
||||
self._test_establish_key_hook(established, before)
|
||||
|
||||
def test_establish_key_twice(self):
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
|
||||
d = w.establish_key()
|
||||
self.assertRaises(InternalError, w.establish_key)
|
||||
del d
|
||||
|
||||
# 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-version", "version-then-key")
|
||||
assert isinstance(success, bool)
|
||||
#print(when, order, success)
|
||||
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
|
||||
w._drop_connection = mock.Mock()
|
||||
w._ws_send_command = mock.Mock()
|
||||
w._mailbox_state = wormhole.OPEN
|
||||
side2 = "side2"
|
||||
d = None
|
||||
|
||||
if success:
|
||||
w._key = b"key"
|
||||
else:
|
||||
w._key = b"wrongkey"
|
||||
plaintext = json.dumps({}).encode("utf-8")
|
||||
data_key = w._derive_phase_key(side2, "version")
|
||||
confmsg = w._encrypt_data(data_key, plaintext)
|
||||
w._key = None
|
||||
|
||||
if when == "early":
|
||||
d = w.verify()
|
||||
self.assertNoResult(d)
|
||||
|
||||
if order == "key-then-version":
|
||||
w._key = b"key"
|
||||
w._event_established_key()
|
||||
else:
|
||||
w._event_received_version(side2, confmsg)
|
||||
|
||||
if when == "middle":
|
||||
d = w.verify()
|
||||
if d:
|
||||
self.assertNoResult(d) # still waiting for other msg
|
||||
|
||||
if order == "version-then-key":
|
||||
w._key = b"key"
|
||||
w._event_established_key()
|
||||
else:
|
||||
w._event_received_version(side2, 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):
|
||||
for when in ("early", "middle", "late"):
|
||||
for order in ("key-then-version", "version-then-key"):
|
||||
for success in (False, True):
|
||||
self._test_verifier(when, order, success)
|
||||
|
||||
|
||||
def test_api_errors(self):
|
||||
# doing things you're not supposed to do
|
||||
pass
|
||||
|
||||
def test_welcome_error(self):
|
||||
# A welcome message could arrive at any time, with an [error] key
|
||||
# that should make us halt. In practice, though, this gets sent as
|
||||
# soon as the connection is established, which limits the possible
|
||||
# states in which we might see it.
|
||||
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
|
||||
w._drop_connection = mock.Mock()
|
||||
ws = MockWebSocket()
|
||||
w._event_connected(ws)
|
||||
w._event_ws_opened(None)
|
||||
self.check_outbound(ws, ["bind"])
|
||||
|
||||
d1 = w.get()
|
||||
d2 = w.verify()
|
||||
d3 = w.get_code()
|
||||
# TODO (tricky): test w.input_code
|
||||
|
||||
self.assertNoResult(d1)
|
||||
self.assertNoResult(d2)
|
||||
self.assertNoResult(d3)
|
||||
|
||||
w._signal_error(WelcomeError("you are not actually welcome"), "pouty")
|
||||
self.failureResultOf(d1, WelcomeError)
|
||||
self.failureResultOf(d2, WelcomeError)
|
||||
self.failureResultOf(d3, WelcomeError)
|
||||
|
||||
# once the error is signalled, all API calls should fail
|
||||
self.assertRaises(WelcomeError, w.send, "foo")
|
||||
self.assertRaises(WelcomeError,
|
||||
w.derive_key, "foo", SecretBox.KEY_SIZE)
|
||||
self.failureResultOf(w.get(), WelcomeError)
|
||||
self.failureResultOf(w.verify(), WelcomeError)
|
||||
|
||||
def test_version_error(self):
|
||||
# we should only receive the "version" message after we receive the
|
||||
# PAKE message, by which point we should know the key. If the
|
||||
# confirmation message doesn't decrypt, we signal an error.
|
||||
timing = DebugTiming()
|
||||
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
|
||||
w._drop_connection = mock.Mock()
|
||||
ws = MockWebSocket()
|
||||
w._event_connected(ws)
|
||||
w._event_ws_opened(None)
|
||||
w.set_code("123-foo-bar")
|
||||
response(w, type="claimed", mailbox="mb456")
|
||||
|
||||
d1 = w.get()
|
||||
d2 = w.verify()
|
||||
self.assertNoResult(d1)
|
||||
self.assertNoResult(d2)
|
||||
|
||||
out = ws.outbound()
|
||||
# ["bind", "claim", "open", "add"]
|
||||
self.assertEqual(len(out), 4)
|
||||
self.assertEqual(out[3]["type"], "add")
|
||||
|
||||
sp2 = SPAKE2_Symmetric(b"", idSymmetric=wormhole.to_bytes(APPID))
|
||||
msg2 = sp2.start()
|
||||
payload = {"pake_v1": bytes_to_hexstr(msg2)}
|
||||
body_hex = bytes_to_hexstr(dict_to_bytes(payload))
|
||||
response(w, type="message", phase="pake", body=body_hex, side="s2")
|
||||
self.assertNoResult(d1)
|
||||
self.assertNoResult(d2) # verify() waits for confirmation
|
||||
|
||||
# sending a random version message will cause a confirmation error
|
||||
confkey = w.derive_key("WRONG", SecretBox.KEY_SIZE)
|
||||
nonce = os.urandom(wormhole.CONFMSG_NONCE_LENGTH)
|
||||
badversion = wormhole.make_confmsg(confkey, nonce)
|
||||
badversion_hex = hexlify(badversion).decode("ascii")
|
||||
response(w, type="message", phase="version", body=badversion_hex,
|
||||
side="s2")
|
||||
|
||||
self.failureResultOf(d1, WrongPasswordError)
|
||||
self.failureResultOf(d2, WrongPasswordError)
|
||||
|
||||
# once the error is signalled, all API calls should fail
|
||||
self.assertRaises(WrongPasswordError, w.send, "foo")
|
||||
self.assertRaises(WrongPasswordError,
|
||||
w.derive_key, "foo", SecretBox.KEY_SIZE)
|
||||
self.failureResultOf(w.get(), WrongPasswordError)
|
||||
self.failureResultOf(w.verify(), WrongPasswordError)
|
||||
Basic.skip = "being replaced by test_wormhole_new"
|
||||
|
||||
# event orderings to exercise:
|
||||
#
|
||||
# * normal sender: set_code, send_phase1, connected, claimed, learn_msg2,
|
||||
|
@ -645,27 +58,105 @@ Basic.skip = "being replaced by test_wormhole_new"
|
|||
# * set_code, then connected
|
||||
# * connected, receive_pake, send_phase, set_code
|
||||
|
||||
class Delegate:
|
||||
def __init__(self):
|
||||
self.code = None
|
||||
self.verifier = None
|
||||
self.messages = []
|
||||
self.closed = None
|
||||
def wormhole_got_code(self, code):
|
||||
self.code = code
|
||||
def wormhole_got_verifier(self, verifier):
|
||||
self.verifier = verifier
|
||||
def wormhole_receive(self, data):
|
||||
self.messages.append(data)
|
||||
def wormhole_closed(self, result):
|
||||
self.closed = result
|
||||
|
||||
class Delegated(ServerBase, unittest.TestCase):
|
||||
|
||||
def test_delegated(self):
|
||||
dg = Delegate()
|
||||
w = wormhole.create(APPID, self.relayurl, reactor, delegate=dg)
|
||||
w.close()
|
||||
|
||||
class Wormholes(ServerBase, unittest.TestCase):
|
||||
# integration test, with a real server
|
||||
|
||||
def doBoth(self, d1, d2):
|
||||
return gatherResults([d1, d2], True)
|
||||
|
||||
@inlineCallbacks
|
||||
def test_allocate_default(self):
|
||||
w1 = wormhole.create(APPID, self.relayurl, reactor)
|
||||
w1.allocate_code()
|
||||
code = yield w1.when_code()
|
||||
mo = re.search(r"^\d+-\w+-\w+$", code)
|
||||
self.assert_(mo, code)
|
||||
# w.close() fails because we closed before connecting
|
||||
yield self.assertFailure(w1.close(), LonelyError)
|
||||
|
||||
@inlineCallbacks
|
||||
def test_allocate_more_words(self):
|
||||
w1 = wormhole.create(APPID, self.relayurl, reactor)
|
||||
w1.allocate_code(3)
|
||||
code = yield w1.when_code()
|
||||
mo = re.search(r"^\d+-\w+-\w+-\w+$", code)
|
||||
self.assert_(mo, code)
|
||||
yield self.assertFailure(w1.close(), LonelyError)
|
||||
|
||||
@inlineCallbacks
|
||||
def test_basic(self):
|
||||
w1 = wormhole.create(APPID, self.relayurl, reactor)
|
||||
#w1.debug_set_trace("W1")
|
||||
w2 = wormhole.create(APPID, self.relayurl, reactor)
|
||||
#w2.debug_set_trace(" W2")
|
||||
w1.allocate_code()
|
||||
code = yield w1.when_code()
|
||||
w2.set_code(code)
|
||||
|
||||
verifier1 = yield w1.when_verified()
|
||||
verifier2 = yield w2.when_verified()
|
||||
self.assertEqual(verifier1, verifier2)
|
||||
|
||||
version1 = yield w1.when_version()
|
||||
version2 = yield w2.when_version()
|
||||
# TODO: add the ability to set app-versions
|
||||
self.assertEqual(version1, {})
|
||||
self.assertEqual(version2, {})
|
||||
|
||||
w1.send(b"data1")
|
||||
w2.send(b"data2")
|
||||
dataX = yield w1.when_received()
|
||||
dataY = yield w2.when_received()
|
||||
self.assertEqual(dataX, b"data2")
|
||||
self.assertEqual(dataY, b"data1")
|
||||
yield w1.close()
|
||||
yield w2.close()
|
||||
|
||||
version1_again = yield w1.when_version()
|
||||
self.assertEqual(version1, version1_again)
|
||||
|
||||
c1 = yield w1.close()
|
||||
self.assertEqual(c1, "happy")
|
||||
c2 = yield w2.close()
|
||||
self.assertEqual(c2, "happy")
|
||||
|
||||
@inlineCallbacks
|
||||
def test_when_code_early(self):
|
||||
w1 = wormhole.create(APPID, self.relayurl, reactor)
|
||||
d = w1.when_code()
|
||||
w1.set_code("1-abc")
|
||||
code = self.successResultOf(d)
|
||||
self.assertEqual(code, "1-abc")
|
||||
yield self.assertFailure(w1.close(), LonelyError)
|
||||
|
||||
@inlineCallbacks
|
||||
def test_when_code_late(self):
|
||||
w1 = wormhole.create(APPID, self.relayurl, reactor)
|
||||
w1.set_code("1-abc")
|
||||
d = w1.when_code()
|
||||
code = self.successResultOf(d)
|
||||
self.assertEqual(code, "1-abc")
|
||||
yield self.assertFailure(w1.close(), LonelyError)
|
||||
|
||||
@inlineCallbacks
|
||||
def test_same_message(self):
|
||||
|
@ -793,6 +284,8 @@ class Wormholes(ServerBase, unittest.TestCase):
|
|||
w1.allocate_code()
|
||||
code = yield w1.when_code()
|
||||
w2.set_code(code+"not")
|
||||
code2 = yield w2.when_code()
|
||||
self.assertNotEqual(code, code2)
|
||||
# That's enough to allow both sides to discover the mismatch, but
|
||||
# only after the confirmation message gets through. API calls that
|
||||
# don't wait will appear to work until the mismatched confirmation
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
from __future__ import print_function, unicode_literals
|
||||
import re
|
||||
from twisted.trial import unittest
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
from .common import ServerBase
|
||||
from .. import wormhole, errors
|
||||
|
||||
APPID = "appid"
|
||||
|
||||
class Delegate:
|
||||
def __init__(self):
|
||||
self.code = None
|
||||
self.verifier = None
|
||||
self.messages = []
|
||||
self.closed = None
|
||||
def wormhole_got_code(self, code):
|
||||
self.code = code
|
||||
def wormhole_got_verifier(self, verifier):
|
||||
self.verifier = verifier
|
||||
def wormhole_receive(self, data):
|
||||
self.messages.append(data)
|
||||
def wormhole_closed(self, result):
|
||||
self.closed = result
|
||||
|
||||
class New(ServerBase, unittest.TestCase):
|
||||
timeout = 2
|
||||
|
||||
@inlineCallbacks
|
||||
def test_allocate(self):
|
||||
w = wormhole.create(APPID, self.relayurl, reactor)
|
||||
#w.debug_set_trace("W1")
|
||||
w.allocate_code(2)
|
||||
code = yield w.when_code()
|
||||
self.assertEqual(type(code), type(""))
|
||||
mo = re.search(r"^\d+-\w+-\w+$", code)
|
||||
self.assert_(mo, code)
|
||||
# w.close() fails because we closed before connecting
|
||||
yield self.assertFailure(w.close(), errors.LonelyError)
|
||||
|
||||
def test_delegated(self):
|
||||
dg = Delegate()
|
||||
w = wormhole.create(APPID, self.relayurl, reactor, delegate=dg)
|
||||
w.close()
|
||||
|
||||
@inlineCallbacks
|
||||
def test_basic(self):
|
||||
w1 = wormhole.create(APPID, self.relayurl, reactor)
|
||||
#w1.debug_set_trace("W1")
|
||||
w1.allocate_code(2)
|
||||
code = yield w1.when_code()
|
||||
mo = re.search(r"^\d+-\w+-\w+$", code)
|
||||
self.assert_(mo, code)
|
||||
w2 = wormhole.create(APPID, self.relayurl, reactor)
|
||||
#w2.debug_set_trace(" W2")
|
||||
w2.set_code(code)
|
||||
code2 = yield w2.when_code()
|
||||
self.assertEqual(code, code2)
|
||||
|
||||
verifier1 = yield w1.when_verified()
|
||||
verifier2 = yield w2.when_verified()
|
||||
self.assertEqual(verifier1, verifier2)
|
||||
|
||||
version1 = yield w1.when_version()
|
||||
version2 = yield w2.when_version()
|
||||
# TODO: add the ability to set app-versions
|
||||
self.assertEqual(version1, {})
|
||||
self.assertEqual(version2, {})
|
||||
|
||||
w1.send(b"data")
|
||||
|
||||
data = yield w2.when_received()
|
||||
self.assertEqual(data, b"data")
|
||||
|
||||
w2.send(b"data2")
|
||||
data2 = yield w1.when_received()
|
||||
self.assertEqual(data2, b"data2")
|
||||
|
||||
version1_again = yield w1.when_version()
|
||||
self.assertEqual(version1, version1_again)
|
||||
|
||||
c1 = yield w1.close()
|
||||
self.assertEqual(c1, "happy")
|
||||
c2 = yield w2.close()
|
||||
self.assertEqual(c2, "happy")
|
||||
|
||||
@inlineCallbacks
|
||||
def test_wrong_password(self):
|
||||
w1 = wormhole.create(APPID, self.relayurl, reactor)
|
||||
#w1.debug_set_trace("W1")
|
||||
w1.allocate_code(2)
|
||||
code = yield w1.when_code()
|
||||
w2 = wormhole.create(APPID, self.relayurl, reactor)
|
||||
w2.set_code(code+"NOT")
|
||||
code2 = yield w2.when_code()
|
||||
self.assertNotEqual(code, code2)
|
||||
|
||||
w1.send(b"data")
|
||||
|
||||
yield self.assertFailure(w2.when_received(), errors.WrongPasswordError)
|
||||
# wait for w1.when_received, because if we close w1 before it has
|
||||
# seen the VERSION message, we could legitimately get LonelyError
|
||||
# instead of WrongPasswordError. w2 didn't send anything, so
|
||||
# w1.when_received wouldn't ever callback, but it will errback when
|
||||
# w1 gets the undecryptable VERSION.
|
||||
yield self.assertFailure(w1.when_received(), errors.WrongPasswordError)
|
||||
yield self.assertFailure(w1.close(), errors.WrongPasswordError)
|
||||
yield self.assertFailure(w2.close(), errors.WrongPasswordError)
|
Loading…
Reference in New Issue
Block a user