implement w.derive_key()

This commit is contained in:
Brian Warner 2017-03-04 10:55:42 +01:00
parent 0474c39bab
commit 60a61c995b
8 changed files with 56 additions and 10 deletions

View File

@ -63,7 +63,7 @@ digraph {
{rank=same; Other S_closed}
Other [shape="box" style="dashed"
label="rx_welcome -> process\nsend -> S.send\ngot_verifier -> W.got_verifier\nallocate -> C.allocate\ninput -> C.input\nset_code -> C.set_code"
label="rx_welcome -> process\nsend -> S.send\ngot_key -> W.got_key\ngot_verifier -> W.got_verifier\nallocate -> C.allocate\ninput -> C.input\nset_code -> C.set_code"
]

View File

@ -35,7 +35,7 @@ digraph {
S1 -> P1_compute [label="got_pake\npake good"]
#S1 -> P_mood_lonely [label="close"]
P1_compute [label="compute_key\nM.add_message(version)\nW.got_verifier\nR.got_key" shape="box"]
P1_compute [label="compute_key\nM.add_message(version)\nB.got_key\nB.got_verifier\nR.got_key" shape="box"]
P1_compute -> S2
S2 [label="S2: know_key" color="green"]

View File

@ -21,7 +21,7 @@ digraph {
Wormhole -> Boss [style="dashed" label="allocate_code\ninput_code\nset_code\nsend\nclose\n(once)"]
#Wormhole -> Boss [color="blue"]
Boss -> Wormhole [style="dashed" label="got_code\ngot_verifier\nreceived (seq)\nclosed\n(once)"]
Boss -> Wormhole [style="dashed" label="got_code\ngot_key\ngot_verifier\nreceived (seq)\nclosed\n(once)"]
#Boss -> Connection [color="blue"]
Boss -> Connection [style="dashed" label="start"]
@ -33,7 +33,7 @@ digraph {
#Boss -> Mailbox [color="blue"]
Mailbox -> Order [style="dashed" label="got_message (once)"]
Boss -> Key [style="dashed" label="got_code"]
Key -> Boss [style="dashed" label="got_verifier\nscared"]
Key -> Boss [style="dashed" label="got_key\ngot_verifier\nscared"]
Order -> Key [style="dashed" label="got_pake"]
Order -> Receive [style="dashed" label="got_message"]
#Boss -> Key [color="blue"]

View File

@ -135,7 +135,7 @@ class Boss(object):
@m.input()
def got_code(self, code): pass
# Key sends (got_verifier, scared)
# Key sends (got_key, got_verifier, scared)
# Receive sends (got_message, happy, scared)
@m.input()
def happy(self): pass
@ -158,6 +158,8 @@ class Boss(object):
@m.input()
def got_phase(self, phase, plaintext): pass
@m.input()
def got_key(self, key): pass
@m.input()
def got_verifier(self, verifier): pass
# Terminator sends closed
@ -205,6 +207,9 @@ class Boss(object):
self._T.close("happy")
@m.output()
def W_got_key(self, key):
self._W.got_key(key)
@m.output()
def W_got_verifier(self, verifier):
self._W.got_verifier(verifier)
@m.output()
@ -238,6 +243,7 @@ class Boss(object):
S1_lonely.upon(scared, enter=S3_closing, outputs=[close_scared])
S1_lonely.upon(close, enter=S3_closing, outputs=[close_lonely])
S1_lonely.upon(send, enter=S1_lonely, outputs=[S_send])
S1_lonely.upon(got_key, enter=S1_lonely, outputs=[W_got_key])
S1_lonely.upon(got_verifier, enter=S1_lonely, outputs=[W_got_verifier])
S1_lonely.upon(rx_error, enter=S3_closing, outputs=[close_error])
S1_lonely.upon(error, enter=S4_closed, outputs=[W_close_with_error])

View File

@ -110,6 +110,7 @@ class Key(object):
assert isinstance(msg2, type(b""))
with self._timing.add("pake2", waiting="crypto"):
key = self._sp.finish(msg2)
self._B.got_key(key)
self._B.got_verifier(derive_key(key, b"wormhole:verifier"))
phase = "version"
data_key = derive_phase_key(key, self._side, phase)

View File

@ -50,3 +50,6 @@ class TransferError(WormholeError):
class NoTorError(WormholeError):
"""--tor was requested, but 'txtorcon' is not installed."""
class NoKeyError(WormholeError):
"""w.derive_key() was called before got_verifier() fired"""

View File

@ -147,7 +147,7 @@ class Key(unittest.TestCase):
def build(self):
events = []
k = _key.Key(u"appid", u"side", timing.DebugTiming())
b = Dummy("b", events, IBoss, "scared", "got_verifier")
b = Dummy("b", events, IBoss, "scared", "got_key", "got_verifier")
m = Dummy("m", events, IMailbox, "add_message")
r = Dummy("r", events, IReceive, "got_key")
k.wire(b, m, r)
@ -168,10 +168,11 @@ class Key(unittest.TestCase):
key2 = sp.finish(msg1_bytes)
msg2 = dict_to_bytes({"pake_v1": bytes_to_hexstr(msg2_bytes)})
k.got_pake(msg2)
self.assertEqual(len(events), 3)
self.assertEqual(events[0][0], "b.got_verifier")
self.assertEqual(events[1][:2], ("m.add_message", "version"))
self.assertEqual(events[2], ("r.got_key", key2))
self.assertEqual(len(events), 4, events)
self.assertEqual(events[0], ("b.got_key", key2))
self.assertEqual(events[1][0], "b.got_verifier")
self.assertEqual(events[2][:2], ("m.add_message", "version"))
self.assertEqual(events[3], ("r.got_key", key2))
def test_bad(self):

View File

@ -9,6 +9,9 @@ from .util import bytes_to_hexstr
from .timing import DebugTiming
from .journal import ImmediateJournal
from ._boss import Boss
from ._key import derive_key
from .errors import NoKeyError
from .util import to_bytes
# We can provide different APIs to different apps:
# * Deferreds
@ -39,6 +42,9 @@ def _log(client_name, machine_name, old_state, input, new_state):
class _DelegatedWormhole(object):
_delegate = attrib()
def __attrs_post_init__(self):
self._key = None
def _set_boss(self, boss):
self._boss = boss
@ -59,6 +65,18 @@ class _DelegatedWormhole(object):
def send(self, plaintext):
self._boss.send(plaintext)
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)
def close(self):
self._boss.close()
@ -69,6 +87,8 @@ class _DelegatedWormhole(object):
# from below
def got_code(self, code):
self._delegate.wormhole_got_code(code)
def got_key(self, key):
self._key = key # for derive_key()
def got_verifier(self, verifier):
self._delegate.wormhole_got_verifier(verifier)
def received(self, plaintext):
@ -84,6 +104,7 @@ class _DeferredWormhole(object):
def __init__(self):
self._code = None
self._code_observers = []
self._key = None
self._verifier = None
self._verifier_observers = []
self._received_data = []
@ -125,6 +146,18 @@ class _DeferredWormhole(object):
# no .serialize in Deferred-mode
def send(self, plaintext):
self._boss.send(plaintext)
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)
def close(self):
# fails with WormholeError unless we established a connection
# (state=="happy"). Fails with WrongPasswordError (a subclass of
@ -144,6 +177,8 @@ class _DeferredWormhole(object):
for d in self._code_observers:
d.callback(code)
self._code_observers[:] = []
def got_key(self, key):
self._key = key # for derive_key()
def got_verifier(self, verifier):
self._verifier = verifier
for d in self._verifier_observers: