2018-04-21 07:30:08 +00:00
|
|
|
from __future__ import absolute_import, print_function, unicode_literals
|
|
|
|
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
|
|
|
|
from attr import attrib, attrs
|
2017-03-03 07:55:59 +00:00
|
|
|
from twisted.python import failure
|
2018-06-30 21:15:06 +00:00
|
|
|
from twisted.internet.task import Cooperator
|
2018-04-21 07:30:08 +00:00
|
|
|
from zope.interface import implementer
|
|
|
|
|
2017-03-03 07:55:59 +00:00
|
|
|
from ._boss import Boss
|
2018-07-02 04:15:16 +00:00
|
|
|
from ._dilation.manager import DILATION_VERSIONS
|
2018-07-01 21:44:45 +00:00
|
|
|
from ._dilation.connector import Connector
|
2018-04-21 07:30:08 +00:00
|
|
|
from ._interfaces import IDeferredWormhole, IWormhole
|
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
|
2018-04-21 07:30:08 +00:00
|
|
|
from .eventual import EventualQueue
|
|
|
|
from .journal import ImmediateJournal
|
|
|
|
from .observer import OneShotObserver, SequenceObserver
|
|
|
|
from .timing import DebugTiming
|
|
|
|
from .util import bytes_to_hexstr, to_bytes
|
|
|
|
from ._version import get_versions
|
|
|
|
|
|
|
|
__version__ = get_versions()['version']
|
|
|
|
del get_versions
|
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-05-12 20:12:36 +00:00
|
|
|
# w.get_code().addCallback(print_code)
|
|
|
|
# w.send_message(data)
|
|
|
|
# w.get_message().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)
|
2017-05-12 20:12:36 +00:00
|
|
|
# w.send_message(data)
|
2017-02-23 23:57:24 +00:00
|
|
|
# app.wormhole_got_code(code)
|
2017-02-24 01:29:56 +00:00
|
|
|
# app.wormhole_got_verifier(verifier)
|
2017-05-12 20:12:36 +00:00
|
|
|
# app.wormhole_got_versions(versions)
|
|
|
|
# app.wormhole_got_message(data)
|
2017-02-23 23:57:24 +00:00
|
|
|
# w.close()
|
|
|
|
# app.wormhole_closed()
|
|
|
|
#
|
|
|
|
# * potential delegate options
|
|
|
|
# wormhole(delegate=app, delegate_prefix="wormhole_",
|
|
|
|
# delegate_args=(args, kwargs))
|
|
|
|
|
2018-04-21 07:30:08 +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)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-04-19 02:49:50 +00:00
|
|
|
def input_code(self):
|
|
|
|
return self._boss.input_code()
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-02-24 02:23:55 +00:00
|
|
|
def set_code(self, code):
|
|
|
|
self._boss.set_code(code)
|
|
|
|
|
2018-04-21 07:30:08 +00:00
|
|
|
# def serialize(self):
|
|
|
|
# s = {"serialized_wormhole_version": 1,
|
|
|
|
# "boss": self._boss.serialize(),
|
|
|
|
# }
|
|
|
|
# return s
|
2017-03-03 22:19:48 +00:00
|
|
|
|
2017-05-12 20:12:36 +00:00
|
|
|
def send_message(self, plaintext):
|
2017-02-24 01:29:56 +00:00
|
|
|
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.
|
|
|
|
"""
|
2018-04-21 07:30:08 +00:00
|
|
|
if not isinstance(purpose, type("")):
|
|
|
|
raise TypeError(type(purpose))
|
|
|
|
if not self._key:
|
|
|
|
raise NoKeyError()
|
2017-03-04 09:55:42 +00:00
|
|
|
return derive_key(self._key, to_bytes(purpose), length)
|
|
|
|
|
2017-02-23 23:57:24 +00:00
|
|
|
def close(self):
|
|
|
|
self._boss.close()
|
|
|
|
|
2018-04-21 07:30:08 +00:00
|
|
|
def debug_set_trace(self,
|
|
|
|
client_name,
|
|
|
|
which="B N M S O K SK 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 23:57:24 +00:00
|
|
|
# from below
|
2017-05-12 20:12:36 +00:00
|
|
|
def got_welcome(self, welcome):
|
|
|
|
self._delegate.wormhole_got_welcome(welcome)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-02-23 23:57:24 +00:00
|
|
|
def got_code(self, code):
|
2017-05-12 20:12:36 +00:00
|
|
|
self._delegate.wormhole_got_code(code)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-03-04 09:55:42 +00:00
|
|
|
def got_key(self, key):
|
2017-05-12 20:12:36 +00:00
|
|
|
self._delegate.wormhole_got_unverified_key(key)
|
2018-04-21 07:30:08 +00:00
|
|
|
self._key = key # for derive_key()
|
|
|
|
|
2017-02-23 23:57:24 +00:00
|
|
|
def got_verifier(self, verifier):
|
2017-05-12 20:12:36 +00:00
|
|
|
self._delegate.wormhole_got_verifier(verifier)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-05-12 20:12:36 +00:00
|
|
|
def got_versions(self, versions):
|
|
|
|
self._delegate.wormhole_got_versions(versions)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-02-24 02:23:55 +00:00
|
|
|
def received(self, plaintext):
|
2017-05-12 20:12:36 +00:00
|
|
|
self._delegate.wormhole_got_message(plaintext)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-02-23 23:57:24 +00:00
|
|
|
def closed(self, result):
|
|
|
|
self._delegate.wormhole_closed(result)
|
|
|
|
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-06-26 10:57:40 +00:00
|
|
|
@implementer(IWormhole, IDeferredWormhole)
|
2017-02-23 23:57:24 +00:00
|
|
|
class _DeferredWormhole(object):
|
2019-02-10 19:44:11 +00:00
|
|
|
def __init__(self, reactor, eq, _enable_dilate=False):
|
2018-06-30 21:15:06 +00:00
|
|
|
self._reactor = reactor
|
2018-02-25 01:48:23 +00:00
|
|
|
self._welcome_observer = OneShotObserver(eq)
|
|
|
|
self._code_observer = OneShotObserver(eq)
|
2017-03-04 09:55:42 +00:00
|
|
|
self._key = None
|
2018-02-25 01:48:23 +00:00
|
|
|
self._key_observer = OneShotObserver(eq)
|
|
|
|
self._verifier_observer = OneShotObserver(eq)
|
|
|
|
self._version_observer = OneShotObserver(eq)
|
|
|
|
self._received_observer = SequenceObserver(eq)
|
|
|
|
self._closed = False
|
|
|
|
self._closed_observer = OneShotObserver(eq)
|
2017-02-23 02:21:47 +00:00
|
|
|
|
2019-02-10 19:44:11 +00:00
|
|
|
self._enable_dilate = _enable_dilate
|
|
|
|
|
2017-02-23 02:21:47 +00:00
|
|
|
def _set_boss(self, boss):
|
|
|
|
self._boss = boss
|
|
|
|
|
|
|
|
# from above
|
2017-05-12 20:12:36 +00:00
|
|
|
def get_code(self):
|
2017-03-07 11:09:06 +00:00
|
|
|
# TODO: consider throwing error unless one of allocate/set/input_code
|
2017-04-07 02:44:27 +00:00
|
|
|
# was called first. It's legit to grab the Deferred before triggering
|
|
|
|
# the process that will cause it to fire, but forbidding that
|
|
|
|
# ordering would make it easier to cause programming errors that
|
|
|
|
# forget to trigger it entirely.
|
2018-02-25 01:48:23 +00:00
|
|
|
return self._code_observer.when_fired()
|
2017-02-23 02:21:47 +00:00
|
|
|
|
2017-05-12 20:12:36 +00:00
|
|
|
def get_welcome(self):
|
2018-02-25 01:48:23 +00:00
|
|
|
return self._welcome_observer.when_fired()
|
2017-05-12 20:12:36 +00:00
|
|
|
|
|
|
|
def get_unverified_key(self):
|
2018-02-25 01:48:23 +00:00
|
|
|
return self._key_observer.when_fired()
|
add w.when_key(), fix w.when_verified() to fire later
Previously, w.when_verified() was documented to fire only after a valid
encrypted message was received, but in fact it fired as soon as the shared
key was derived (before any encrypted messages are seen, so no actual
"verification" could occur yet).
This fixes that, and also adds a new w.when_key() API call which fires at the
earlier point. Having something which fires early is useful for the CLI
commands that want to print a pacifier message when the peer is responding
slowly. In particular it helps detect the case where 'wormhole send' has quit
early (after depositing the PAKE message on the server, but before the
receiver has started). In this case, the receiver will compute the shared
key, but then wait forever hoping for a VERSION that will never come. By
starting a timer when w.when_key() fires, and cancelling it when
w.when_verified() fires, we have a good place to tell the user that something
is taking longer than it should have.
This shifts responsibility for notifying Boss.got_verifier, out of Key and
into Receive, since Receive is what notices the first valid encrypted
message. It also shifts the Boss's ordering expectations: it now receives
B.happy() before B.got_verifier(), and consequently got_verifier ought to
arrive in the S2_happy state rather than S1_lonely.
2017-04-07 01:27:41 +00:00
|
|
|
|
2017-05-12 20:12:36 +00:00
|
|
|
def get_verifier(self):
|
2018-02-25 01:48:23 +00:00
|
|
|
return self._verifier_observer.when_fired()
|
2017-02-23 02:21:47 +00:00
|
|
|
|
2017-05-12 20:12:36 +00:00
|
|
|
def get_versions(self):
|
2018-02-25 01:48:23 +00:00
|
|
|
return self._version_observer.when_fired()
|
2017-03-04 10:36:19 +00:00
|
|
|
|
2017-05-12 20:12:36 +00:00
|
|
|
def get_message(self):
|
2018-02-25 01:48:23 +00:00
|
|
|
return self._received_observer.when_next_event()
|
2017-02-24 02:23:55 +00:00
|
|
|
|
|
|
|
def allocate_code(self, code_length=2):
|
|
|
|
self._boss.allocate_code(code_length)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-03-19 22:09:26 +00:00
|
|
|
def input_code(self):
|
|
|
|
return self._boss.input_code()
|
2018-04-21 07:30:08 +00:00
|
|
|
|
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-05-12 20:12:36 +00:00
|
|
|
|
|
|
|
def send_message(self, plaintext):
|
2017-02-24 01:29:56 +00:00
|
|
|
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
|
add w.when_key(), fix w.when_verified() to fire later
Previously, w.when_verified() was documented to fire only after a valid
encrypted message was received, but in fact it fired as soon as the shared
key was derived (before any encrypted messages are seen, so no actual
"verification" could occur yet).
This fixes that, and also adds a new w.when_key() API call which fires at the
earlier point. Having something which fires early is useful for the CLI
commands that want to print a pacifier message when the peer is responding
slowly. In particular it helps detect the case where 'wormhole send' has quit
early (after depositing the PAKE message on the server, but before the
receiver has started). In this case, the receiver will compute the shared
key, but then wait forever hoping for a VERSION that will never come. By
starting a timer when w.when_key() fires, and cancelling it when
w.when_verified() fires, we have a good place to tell the user that something
is taking longer than it should have.
This shifts responsibility for notifying Boss.got_verifier, out of Key and
into Receive, since Receive is what notices the first valid encrypted
message. It also shifts the Boss's ordering expectations: it now receives
B.happy() before B.got_verifier(), and consequently got_verifier ought to
arrive in the S2_happy state rather than S1_lonely.
2017-04-07 01:27:41 +00:00
|
|
|
cannot be called until when_verified() has fired, nor after close()
|
2017-03-04 09:55:42 +00:00
|
|
|
was called.
|
|
|
|
"""
|
2018-04-21 07:30:08 +00:00
|
|
|
if not isinstance(purpose, type("")):
|
|
|
|
raise TypeError(type(purpose))
|
|
|
|
if not self._key:
|
|
|
|
raise NoKeyError()
|
2017-03-04 09:55:42 +00:00
|
|
|
return derive_key(self._key, to_bytes(purpose), length)
|
|
|
|
|
2019-08-12 02:37:10 +00:00
|
|
|
def dilate(self, transit_relay_location=None, no_listen=False):
|
2019-02-10 19:44:11 +00:00
|
|
|
if not self._enable_dilate:
|
|
|
|
raise NotImplementedError
|
2019-08-12 02:37:10 +00:00
|
|
|
return self._boss.dilate(transit_relay_location, no_listen) # fires with (endpoints)
|
2018-07-01 21:44:45 +00:00
|
|
|
|
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".
|
2018-04-21 07:30:08 +00:00
|
|
|
d = self._closed_observer.when_fired() # maybe Failure
|
2018-02-25 01:48:23 +00:00
|
|
|
if not self._closed:
|
2018-04-21 07:30:08 +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
|
|
|
|
2018-04-21 07:30:08 +00:00
|
|
|
def debug_set_trace(self,
|
|
|
|
client_name,
|
2018-02-14 08:32:49 +00:00
|
|
|
which="B N M S O K SK R RC L A I 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
|
2017-05-12 20:12:36 +00:00
|
|
|
def got_welcome(self, welcome):
|
2018-02-25 01:48:23 +00:00
|
|
|
self._welcome_observer.fire_if_not_fired(welcome)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-02-23 02:21:47 +00:00
|
|
|
def got_code(self, code):
|
2018-02-25 01:48:23 +00:00
|
|
|
self._code_observer.fire_if_not_fired(code)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-03-04 09:55:42 +00:00
|
|
|
def got_key(self, key):
|
2018-04-21 07:30:08 +00:00
|
|
|
self._key = key # for derive_key()
|
2018-02-25 01:48:23 +00:00
|
|
|
self._key_observer.fire_if_not_fired(key)
|
2017-05-12 20:12:36 +00:00
|
|
|
|
2017-02-23 02:21:47 +00:00
|
|
|
def got_verifier(self, verifier):
|
2018-02-25 01:48:23 +00:00
|
|
|
self._verifier_observer.fire_if_not_fired(verifier)
|
2018-04-21 07:30:08 +00:00
|
|
|
|
2017-05-12 20:12:36 +00:00
|
|
|
def got_versions(self, versions):
|
2018-02-25 01:48:23 +00:00
|
|
|
self._version_observer.fire_if_not_fired(versions)
|
2017-02-23 02:21:47 +00:00
|
|
|
|
2017-02-24 02:23:55 +00:00
|
|
|
def received(self, plaintext):
|
2018-02-25 01:48:23 +00:00
|
|
|
self._received_observer.fire(plaintext)
|
2017-02-23 02:21:47 +00:00
|
|
|
|
|
|
|
def closed(self, result):
|
2018-02-25 01:48:23 +00:00
|
|
|
self._closed = True
|
2018-04-21 07:30:08 +00:00
|
|
|
# print("closed", result, type(result), file=sys.stderr)
|
2017-03-03 07:55:59 +00:00
|
|
|
if isinstance(result, Exception):
|
2018-02-25 01:48:23 +00:00
|
|
|
# everything pending gets an error, including close()
|
|
|
|
f = failure.Failure(result)
|
|
|
|
self._closed_observer.error(f)
|
2017-02-25 02:30:00 +00:00
|
|
|
else:
|
2018-02-25 01:48:23 +00:00
|
|
|
# everything pending except close() gets an error:
|
|
|
|
# w.get_code()/welcome/unverified_key/verifier/versions/message
|
|
|
|
f = failure.Failure(WormholeClosed(result))
|
2017-03-03 07:55:59 +00:00
|
|
|
# but w.close() only gets error if we're unhappy
|
2018-02-25 01:48:23 +00:00
|
|
|
self._closed_observer.fire_if_not_fired(result)
|
|
|
|
self._welcome_observer.error(f)
|
|
|
|
self._code_observer.error(f)
|
|
|
|
self._key_observer.error(f)
|
|
|
|
self._verifier_observer.error(f)
|
|
|
|
self._version_observer.error(f)
|
|
|
|
self._received_observer.fire(f)
|
2017-02-23 02:21:47 +00:00
|
|
|
|
2017-03-04 11:43:42 +00:00
|
|
|
|
2018-04-21 07:30:08 +00:00
|
|
|
def create(
|
|
|
|
appid,
|
|
|
|
relay_url,
|
|
|
|
reactor, # use keyword args for everything else
|
|
|
|
versions={},
|
|
|
|
delegate=None,
|
|
|
|
journal=None,
|
|
|
|
tor=None,
|
|
|
|
timing=None,
|
|
|
|
stderr=sys.stderr,
|
2019-02-10 19:44:11 +00:00
|
|
|
_eventual_queue=None,
|
|
|
|
_enable_dilate=False):
|
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()
|
2018-02-25 01:48:23 +00:00
|
|
|
eq = _eventual_queue or EventualQueue(reactor)
|
2018-06-30 21:15:06 +00:00
|
|
|
cooperator = Cooperator(scheduler=eq.eventually)
|
2017-02-23 23:57:24 +00:00
|
|
|
if delegate:
|
|
|
|
w = _DelegatedWormhole(delegate)
|
|
|
|
else:
|
2019-02-10 19:44:11 +00:00
|
|
|
w = _DeferredWormhole(reactor, eq, _enable_dilate=_enable_dilate)
|
2018-07-01 21:44:45 +00:00
|
|
|
# this indicates Wormhole capabilities
|
|
|
|
wormhole_versions = {
|
2018-07-02 04:15:16 +00:00
|
|
|
"can-dilate": DILATION_VERSIONS,
|
2018-07-01 21:44:45 +00:00
|
|
|
"dilation-abilities": Connector.get_connection_abilities(),
|
2018-06-30 23:24:28 +00:00
|
|
|
}
|
2019-02-10 19:44:11 +00:00
|
|
|
if not _enable_dilate:
|
|
|
|
wormhole_versions = {} # don't advertise Dilation yet: not ready
|
2018-06-30 23:24:28 +00:00
|
|
|
wormhole_versions["app_versions"] = versions # app-specific capabilities
|
2018-04-04 00:10:46 +00:00
|
|
|
v = __version__
|
|
|
|
if isinstance(v, type(b"")):
|
|
|
|
v = v.decode("utf-8", errors="replace")
|
|
|
|
client_version = ("python", v)
|
2018-03-23 16:33:48 +00:00
|
|
|
b = Boss(w, side, relay_url, appid, wormhole_versions, client_version,
|
2018-06-30 21:15:06 +00:00
|
|
|
reactor, eq, cooperator, journal, tor, 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
|
|
|
|
|
2018-04-21 07:30:08 +00:00
|
|
|
|
|
|
|
# def from_serialized(serialized, reactor, delegate,
|
|
|
|
# journal=None, tor=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.
|