2016-06-04 06:07:50 +00:00
|
|
|
from __future__ import print_function, absolute_import, unicode_literals
|
2017-02-24 01:29:56 +00:00
|
|
|
import os, sys
|
2017-02-23 23:57:24 +00:00
|
|
|
from attr import attrs, attrib
|
2017-02-24 01:29:56 +00:00
|
|
|
from zope.interface import implementer
|
2017-03-03 07:55:59 +00:00
|
|
|
from twisted.python import failure
|
2017-02-24 01:29:56 +00:00
|
|
|
from twisted.internet import defer
|
|
|
|
from ._interfaces import IWormhole
|
|
|
|
from .util import bytes_to_hexstr
|
2017-02-23 01:02:01 +00:00
|
|
|
from .timing import DebugTiming
|
2017-02-22 20:51:53 +00:00
|
|
|
from .journal import ImmediateJournal
|
2017-03-03 07:55:59 +00:00
|
|
|
from ._boss import Boss
|
2017-03-04 09:55:42 +00:00
|
|
|
from ._key import derive_key
|
2017-04-03 21:23:03 +00:00
|
|
|
from .errors import NoKeyError, WormholeClosed
|
2017-03-04 09:55:42 +00:00
|
|
|
from .util import to_bytes
|
2016-05-21 01:49:20 +00:00
|
|
|
|
2017-02-23 23:57:24 +00:00
|
|
|
# We can provide different APIs to different apps:
|
|
|
|
# * Deferreds
|
2017-03-04 11:43:42 +00:00
|
|
|
# w.when_code().addCallback(print_code)
|
2017-02-23 23:57:24 +00:00
|
|
|
# w.send(data)
|
2017-03-04 11:43:42 +00:00
|
|
|
# w.when_received().addCallback(got_data)
|
2017-02-23 23:57:24 +00:00
|
|
|
# w.close().addCallback(closed)
|
|
|
|
|
|
|
|
# * delegate callbacks (better for journaled environments)
|
|
|
|
# w = wormhole(delegate=app)
|
|
|
|
# w.send(data)
|
|
|
|
# app.wormhole_got_code(code)
|
2017-02-24 01:29:56 +00:00
|
|
|
# app.wormhole_got_verifier(verifier)
|
2017-03-07 11:34:36 +00:00
|
|
|
# app.wormhole_got_version(versions)
|
2017-02-23 23:57:24 +00:00
|
|
|
# app.wormhole_receive(data)
|
|
|
|
# w.close()
|
|
|
|
# app.wormhole_closed()
|
|
|
|
#
|
|
|
|
# * potential delegate options
|
|
|
|
# wormhole(delegate=app, delegate_prefix="wormhole_",
|
|
|
|
# delegate_args=(args, kwargs))
|
|
|
|
|
2017-03-04 11:43:42 +00:00
|
|
|
class _WelcomeHandler:
|
2017-04-03 21:23:03 +00:00
|
|
|
def __init__(self, url, stderr=sys.stderr):
|
|
|
|
self.relay_url = url
|
|
|
|
self.stderr = stderr
|
2017-03-04 11:43:42 +00:00
|
|
|
|
|
|
|
def handle_welcome(self, welcome):
|
|
|
|
if "motd" in welcome:
|
|
|
|
motd_lines = welcome["motd"].splitlines()
|
|
|
|
motd_formatted = "\n ".join(motd_lines)
|
|
|
|
print("Server (at %s) says:\n %s" %
|
2017-04-03 21:23:03 +00:00
|
|
|
(self.relay_url, motd_formatted), file=self.stderr)
|
2017-03-04 11:43:42 +00:00
|
|
|
|
2017-02-23 23:57:24 +00:00
|
|
|
@attrs
|
2017-02-24 01:29:56 +00:00
|
|
|
@implementer(IWormhole)
|
2017-02-23 23:57:24 +00:00
|
|
|
class _DelegatedWormhole(object):
|
|
|
|
_delegate = attrib()
|
|
|
|
|
2017-03-04 09:55:42 +00:00
|
|
|
def __attrs_post_init__(self):
|
|
|
|
self._key = None
|
|
|
|
|
2017-02-23 23:57:24 +00:00
|
|
|
def _set_boss(self, boss):
|
|
|
|
self._boss = boss
|
|
|
|
|
|
|
|
# from above
|
2017-02-24 02:23:55 +00:00
|
|
|
|
|
|
|
def allocate_code(self, code_length=2):
|
|
|
|
self._boss.allocate_code(code_length)
|
|
|
|
def input_code(self, stdio):
|
|
|
|
self._boss.input_code(stdio)
|
|
|
|
def set_code(self, code):
|
|
|
|
self._boss.set_code(code)
|
|
|
|
|
2017-03-03 22:19:48 +00:00
|
|
|
def serialize(self):
|
|
|
|
s = {"serialized_wormhole_version": 1,
|
|
|
|
"boss": self._boss.serialize(),
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
|
2017-02-24 01:29:56 +00:00
|
|
|
def send(self, plaintext):
|
|
|
|
self._boss.send(plaintext)
|
2017-03-04 09:55:42 +00:00
|
|
|
|
|
|
|
def derive_key(self, purpose, length):
|
|
|
|
"""Derive a new key from the established wormhole channel for some
|
|
|
|
other purpose. This is a deterministic randomized function of the
|
|
|
|
session key and the 'purpose' string (unicode/py3-string). This
|
|
|
|
cannot be called until when_verifier() has fired, nor after close()
|
|
|
|
was called.
|
|
|
|
"""
|
|
|
|
if not isinstance(purpose, type("")): raise TypeError(type(purpose))
|
|
|
|
if not self._key: raise NoKeyError()
|
|
|
|
return derive_key(self._key, to_bytes(purpose), length)
|
|
|
|
|
2017-02-23 23:57:24 +00:00
|
|
|
def close(self):
|
|
|
|
self._boss.close()
|
|
|
|
|
2017-04-05 04:03:34 +00:00
|
|
|
def debug_set_trace(self, client_name, which="B N M S O K R RC L C T",
|
|
|
|
file=sys.stderr):
|
|
|
|
self._boss._set_trace(client_name, which, file)
|
2017-03-02 03:55:13 +00:00
|
|
|
|
2017-02-23 23:57:24 +00:00
|
|
|
# from below
|
|
|
|
def got_code(self, code):
|
2017-03-07 11:34:36 +00:00
|
|
|
self._delegate.wormhole_code(code)
|
2017-03-04 09:55:42 +00:00
|
|
|
def got_key(self, key):
|
|
|
|
self._key = key # for derive_key()
|
2017-02-23 23:57:24 +00:00
|
|
|
def got_verifier(self, verifier):
|
2017-03-07 11:34:36 +00:00
|
|
|
self._delegate.wormhole_verified(verifier)
|
2017-03-19 22:09:26 +00:00
|
|
|
def got_version(self, versions):
|
2017-03-07 11:34:36 +00:00
|
|
|
self._delegate.wormhole_version(versions)
|
2017-02-24 02:23:55 +00:00
|
|
|
def received(self, plaintext):
|
|
|
|
self._delegate.wormhole_received(plaintext)
|
2017-02-23 23:57:24 +00:00
|
|
|
def closed(self, result):
|
|
|
|
self._delegate.wormhole_closed(result)
|
|
|
|
|
2017-02-24 01:29:56 +00:00
|
|
|
@implementer(IWormhole)
|
2017-02-23 23:57:24 +00:00
|
|
|
class _DeferredWormhole(object):
|
2017-02-23 02:21:47 +00:00
|
|
|
def __init__(self):
|
|
|
|
self._code = None
|
|
|
|
self._code_observers = []
|
2017-03-04 09:55:42 +00:00
|
|
|
self._key = None
|
2017-02-23 02:21:47 +00:00
|
|
|
self._verifier = None
|
|
|
|
self._verifier_observers = []
|
2017-03-07 11:34:36 +00:00
|
|
|
self._versions = None
|
2017-03-04 10:36:19 +00:00
|
|
|
self._version_observers = []
|
2017-02-24 02:23:55 +00:00
|
|
|
self._received_data = []
|
|
|
|
self._received_observers = []
|
2017-03-07 11:09:06 +00:00
|
|
|
self._observer_result = None
|
2017-03-04 22:05:52 +00:00
|
|
|
self._closed_result = None
|
2017-02-25 02:30:00 +00:00
|
|
|
self._closed_observers = []
|
2017-02-23 02:21:47 +00:00
|
|
|
|
|
|
|
def _set_boss(self, boss):
|
|
|
|
self._boss = boss
|
|
|
|
|
|
|
|
# from above
|
|
|
|
def when_code(self):
|
2017-03-07 11:09:06 +00:00
|
|
|
# TODO: consider throwing error unless one of allocate/set/input_code
|
|
|
|
# was called first
|
|
|
|
if self._observer_result is not None:
|
|
|
|
return defer.fail(self._observer_result)
|
|
|
|
if self._code is not None:
|
2017-02-23 02:21:47 +00:00
|
|
|
return defer.succeed(self._code)
|
|
|
|
d = defer.Deferred()
|
|
|
|
self._code_observers.append(d)
|
|
|
|
return d
|
|
|
|
|
2017-03-07 11:34:36 +00:00
|
|
|
def when_verified(self):
|
2017-03-07 11:09:06 +00:00
|
|
|
if self._observer_result is not None:
|
|
|
|
return defer.fail(self._observer_result)
|
|
|
|
if self._verifier is not None:
|
2017-02-23 02:21:47 +00:00
|
|
|
return defer.succeed(self._verifier)
|
|
|
|
d = defer.Deferred()
|
|
|
|
self._verifier_observers.append(d)
|
|
|
|
return d
|
|
|
|
|
2017-03-04 10:36:19 +00:00
|
|
|
def when_version(self):
|
2017-03-07 11:09:06 +00:00
|
|
|
if self._observer_result is not None:
|
|
|
|
return defer.fail(self._observer_result)
|
2017-03-07 11:34:36 +00:00
|
|
|
if self._versions is not None:
|
|
|
|
return defer.succeed(self._versions)
|
2017-03-04 10:36:19 +00:00
|
|
|
d = defer.Deferred()
|
|
|
|
self._version_observers.append(d)
|
|
|
|
return d
|
|
|
|
|
2017-02-24 02:23:55 +00:00
|
|
|
def when_received(self):
|
2017-03-07 11:09:06 +00:00
|
|
|
if self._observer_result is not None:
|
|
|
|
return defer.fail(self._observer_result)
|
2017-02-24 02:23:55 +00:00
|
|
|
if self._received_data:
|
|
|
|
return defer.succeed(self._received_data.pop(0))
|
|
|
|
d = defer.Deferred()
|
|
|
|
self._received_observers.append(d)
|
|
|
|
return d
|
|
|
|
|
|
|
|
def allocate_code(self, code_length=2):
|
|
|
|
self._boss.allocate_code(code_length)
|
2017-03-19 22:09:26 +00:00
|
|
|
def input_code(self):
|
|
|
|
return self._boss.input_code()
|
2017-02-24 02:23:55 +00:00
|
|
|
def set_code(self, code):
|
|
|
|
self._boss.set_code(code)
|
|
|
|
|
2017-03-03 22:19:48 +00:00
|
|
|
# no .serialize in Deferred-mode
|
2017-02-24 01:29:56 +00:00
|
|
|
def send(self, plaintext):
|
|
|
|
self._boss.send(plaintext)
|
2017-03-04 09:55:42 +00:00
|
|
|
|
|
|
|
def derive_key(self, purpose, length):
|
|
|
|
"""Derive a new key from the established wormhole channel for some
|
|
|
|
other purpose. This is a deterministic randomized function of the
|
|
|
|
session key and the 'purpose' string (unicode/py3-string). This
|
|
|
|
cannot be called until when_verifier() has fired, nor after close()
|
|
|
|
was called.
|
|
|
|
"""
|
|
|
|
if not isinstance(purpose, type("")): raise TypeError(type(purpose))
|
|
|
|
if not self._key: raise NoKeyError()
|
|
|
|
return derive_key(self._key, to_bytes(purpose), length)
|
|
|
|
|
2017-02-23 02:21:47 +00:00
|
|
|
def close(self):
|
2017-03-03 07:55:59 +00:00
|
|
|
# fails with WormholeError unless we established a connection
|
|
|
|
# (state=="happy"). Fails with WrongPasswordError (a subclass of
|
|
|
|
# WormholeError) if state=="scary".
|
2017-03-04 22:05:52 +00:00
|
|
|
if self._closed_result:
|
|
|
|
return defer.succeed(self._closed_result) # maybe Failure
|
2017-02-25 02:30:00 +00:00
|
|
|
d = defer.Deferred()
|
|
|
|
self._closed_observers.append(d)
|
2017-03-07 11:09:06 +00:00
|
|
|
self._boss.close() # only need to close if it wasn't already
|
2017-02-25 02:30:00 +00:00
|
|
|
return d
|
2017-02-23 02:21:47 +00:00
|
|
|
|
2017-03-03 14:22:40 +00:00
|
|
|
def debug_set_trace(self, client_name, which="B N M S O K R RC L C T",
|
2017-04-05 04:03:34 +00:00
|
|
|
file=sys.stderr):
|
|
|
|
self._boss._set_trace(client_name, which, file)
|
2017-03-02 03:55:13 +00:00
|
|
|
|
2017-02-23 02:21:47 +00:00
|
|
|
# from below
|
|
|
|
def got_code(self, code):
|
|
|
|
self._code = code
|
|
|
|
for d in self._code_observers:
|
|
|
|
d.callback(code)
|
|
|
|
self._code_observers[:] = []
|
2017-03-04 09:55:42 +00:00
|
|
|
def got_key(self, key):
|
|
|
|
self._key = key # for derive_key()
|
2017-02-23 02:21:47 +00:00
|
|
|
def got_verifier(self, verifier):
|
|
|
|
self._verifier = verifier
|
|
|
|
for d in self._verifier_observers:
|
|
|
|
d.callback(verifier)
|
|
|
|
self._verifier_observers[:] = []
|
2017-03-19 22:09:26 +00:00
|
|
|
def got_version(self, versions):
|
2017-03-07 11:34:36 +00:00
|
|
|
self._versions = versions
|
2017-03-04 10:36:19 +00:00
|
|
|
for d in self._version_observers:
|
2017-03-07 11:34:36 +00:00
|
|
|
d.callback(versions)
|
2017-03-04 10:36:19 +00:00
|
|
|
self._version_observers[:] = []
|
2017-02-23 02:21:47 +00:00
|
|
|
|
2017-02-24 02:23:55 +00:00
|
|
|
def received(self, plaintext):
|
|
|
|
if self._received_observers:
|
|
|
|
self._received_observers.pop(0).callback(plaintext)
|
|
|
|
return
|
|
|
|
self._received_data.append(plaintext)
|
2017-02-23 02:21:47 +00:00
|
|
|
|
|
|
|
def closed(self, result):
|
2017-03-03 07:55:59 +00:00
|
|
|
#print("closed", result, type(result))
|
|
|
|
if isinstance(result, Exception):
|
2017-03-07 11:09:06 +00:00
|
|
|
self._observer_result = self._closed_result = failure.Failure(result)
|
2017-02-25 02:30:00 +00:00
|
|
|
else:
|
2017-03-04 10:36:19 +00:00
|
|
|
# pending w.verify()/w.version()/w.read() get an error
|
2017-03-07 11:09:06 +00:00
|
|
|
self._observer_result = WormholeClosed(result)
|
2017-03-03 07:55:59 +00:00
|
|
|
# but w.close() only gets error if we're unhappy
|
2017-03-04 22:05:52 +00:00
|
|
|
self._closed_result = result
|
2017-02-25 02:30:00 +00:00
|
|
|
for d in self._verifier_observers:
|
2017-03-07 11:09:06 +00:00
|
|
|
d.errback(self._observer_result)
|
2017-03-04 10:36:19 +00:00
|
|
|
for d in self._version_observers:
|
2017-03-07 11:09:06 +00:00
|
|
|
d.errback(self._observer_result)
|
2017-02-25 02:30:00 +00:00
|
|
|
for d in self._received_observers:
|
2017-03-07 11:09:06 +00:00
|
|
|
d.errback(self._observer_result)
|
2017-02-25 02:30:00 +00:00
|
|
|
for d in self._closed_observers:
|
2017-03-04 22:05:52 +00:00
|
|
|
d.callback(self._closed_result)
|
2017-02-23 02:21:47 +00:00
|
|
|
|
2017-03-04 11:43:42 +00:00
|
|
|
|
2017-04-06 19:09:24 +00:00
|
|
|
def create(appid, relay_url, reactor, # use keyword args for everything else
|
|
|
|
versions={},
|
2017-03-07 11:34:36 +00:00
|
|
|
delegate=None, journal=None, tor_manager=None,
|
|
|
|
timing=None, welcome_handler=None,
|
2017-03-04 11:43:42 +00:00
|
|
|
stderr=sys.stderr):
|
2016-05-23 01:40:44 +00:00
|
|
|
timing = timing or DebugTiming()
|
2017-02-23 02:21:47 +00:00
|
|
|
side = bytes_to_hexstr(os.urandom(5))
|
|
|
|
journal = journal or ImmediateJournal()
|
2017-03-04 11:43:42 +00:00
|
|
|
if not welcome_handler:
|
2017-04-03 21:23:03 +00:00
|
|
|
welcome_handler = _WelcomeHandler(relay_url).handle_welcome
|
2017-02-23 23:57:24 +00:00
|
|
|
if delegate:
|
|
|
|
w = _DelegatedWormhole(delegate)
|
|
|
|
else:
|
|
|
|
w = _DeferredWormhole()
|
2017-03-07 11:34:36 +00:00
|
|
|
wormhole_versions = {} # will be used to indicate Wormhole capabilities
|
|
|
|
wormhole_versions["app_versions"] = versions # app-specific capabilities
|
|
|
|
b = Boss(w, side, relay_url, appid, wormhole_versions,
|
|
|
|
welcome_handler, reactor, journal,
|
2017-03-04 12:07:31 +00:00
|
|
|
tor_manager, timing)
|
2017-02-23 02:21:47 +00:00
|
|
|
w._set_boss(b)
|
|
|
|
b.start()
|
2016-05-21 01:49:20 +00:00
|
|
|
return w
|
|
|
|
|
2017-03-03 22:19:48 +00:00
|
|
|
def from_serialized(serialized, reactor, delegate,
|
|
|
|
journal=None, tor_manager=None,
|
|
|
|
timing=None, stderr=sys.stderr):
|
|
|
|
assert serialized["serialized_wormhole_version"] == 1
|
|
|
|
timing = timing or DebugTiming()
|
|
|
|
w = _DelegatedWormhole(delegate)
|
|
|
|
# now unpack state machines, including the SPAKE2 in Key
|
|
|
|
b = Boss.from_serialized(w, serialized["boss"], reactor, journal, timing)
|
|
|
|
w._set_boss(b)
|
|
|
|
b.start() # ??
|
|
|
|
raise NotImplemented
|
|
|
|
# should the new Wormhole call got_code? only if it wasn't called before.
|