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} {rank=same; Other S_closed}
Other [shape="box" style="dashed" 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 -> P1_compute [label="got_pake\npake good"]
#S1 -> P_mood_lonely [label="close"] #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 P1_compute -> S2
S2 [label="S2: know_key" color="green"] 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 [style="dashed" label="allocate_code\ninput_code\nset_code\nsend\nclose\n(once)"]
#Wormhole -> Boss [color="blue"] #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 [color="blue"]
Boss -> Connection [style="dashed" label="start"] Boss -> Connection [style="dashed" label="start"]
@ -33,7 +33,7 @@ digraph {
#Boss -> Mailbox [color="blue"] #Boss -> Mailbox [color="blue"]
Mailbox -> Order [style="dashed" label="got_message (once)"] Mailbox -> Order [style="dashed" label="got_message (once)"]
Boss -> Key [style="dashed" label="got_code"] 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 -> Key [style="dashed" label="got_pake"]
Order -> Receive [style="dashed" label="got_message"] Order -> Receive [style="dashed" label="got_message"]
#Boss -> Key [color="blue"] #Boss -> Key [color="blue"]

View File

@ -135,7 +135,7 @@ class Boss(object):
@m.input() @m.input()
def got_code(self, code): pass def got_code(self, code): pass
# Key sends (got_verifier, scared) # Key sends (got_key, got_verifier, scared)
# Receive sends (got_message, happy, scared) # Receive sends (got_message, happy, scared)
@m.input() @m.input()
def happy(self): pass def happy(self): pass
@ -158,6 +158,8 @@ class Boss(object):
@m.input() @m.input()
def got_phase(self, phase, plaintext): pass def got_phase(self, phase, plaintext): pass
@m.input() @m.input()
def got_key(self, key): pass
@m.input()
def got_verifier(self, verifier): pass def got_verifier(self, verifier): pass
# Terminator sends closed # Terminator sends closed
@ -205,6 +207,9 @@ class Boss(object):
self._T.close("happy") self._T.close("happy")
@m.output() @m.output()
def W_got_key(self, key):
self._W.got_key(key)
@m.output()
def W_got_verifier(self, verifier): def W_got_verifier(self, verifier):
self._W.got_verifier(verifier) self._W.got_verifier(verifier)
@m.output() @m.output()
@ -238,6 +243,7 @@ class Boss(object):
S1_lonely.upon(scared, enter=S3_closing, outputs=[close_scared]) S1_lonely.upon(scared, enter=S3_closing, outputs=[close_scared])
S1_lonely.upon(close, enter=S3_closing, outputs=[close_lonely]) S1_lonely.upon(close, enter=S3_closing, outputs=[close_lonely])
S1_lonely.upon(send, enter=S1_lonely, outputs=[S_send]) 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(got_verifier, enter=S1_lonely, outputs=[W_got_verifier])
S1_lonely.upon(rx_error, enter=S3_closing, outputs=[close_error]) S1_lonely.upon(rx_error, enter=S3_closing, outputs=[close_error])
S1_lonely.upon(error, enter=S4_closed, outputs=[W_close_with_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"")) assert isinstance(msg2, type(b""))
with self._timing.add("pake2", waiting="crypto"): with self._timing.add("pake2", waiting="crypto"):
key = self._sp.finish(msg2) key = self._sp.finish(msg2)
self._B.got_key(key)
self._B.got_verifier(derive_key(key, b"wormhole:verifier")) self._B.got_verifier(derive_key(key, b"wormhole:verifier"))
phase = "version" phase = "version"
data_key = derive_phase_key(key, self._side, phase) data_key = derive_phase_key(key, self._side, phase)

View File

@ -50,3 +50,6 @@ class TransferError(WormholeError):
class NoTorError(WormholeError): class NoTorError(WormholeError):
"""--tor was requested, but 'txtorcon' is not installed.""" """--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): def build(self):
events = [] events = []
k = _key.Key(u"appid", u"side", timing.DebugTiming()) 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") m = Dummy("m", events, IMailbox, "add_message")
r = Dummy("r", events, IReceive, "got_key") r = Dummy("r", events, IReceive, "got_key")
k.wire(b, m, r) k.wire(b, m, r)
@ -168,10 +168,11 @@ class Key(unittest.TestCase):
key2 = sp.finish(msg1_bytes) key2 = sp.finish(msg1_bytes)
msg2 = dict_to_bytes({"pake_v1": bytes_to_hexstr(msg2_bytes)}) msg2 = dict_to_bytes({"pake_v1": bytes_to_hexstr(msg2_bytes)})
k.got_pake(msg2) k.got_pake(msg2)
self.assertEqual(len(events), 3) self.assertEqual(len(events), 4, events)
self.assertEqual(events[0][0], "b.got_verifier") self.assertEqual(events[0], ("b.got_key", key2))
self.assertEqual(events[1][:2], ("m.add_message", "version")) self.assertEqual(events[1][0], "b.got_verifier")
self.assertEqual(events[2], ("r.got_key", key2)) self.assertEqual(events[2][:2], ("m.add_message", "version"))
self.assertEqual(events[3], ("r.got_key", key2))
def test_bad(self): def test_bad(self):

View File

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