check key-confirmation messages, if present

This commit is contained in:
Brian Warner 2015-11-16 18:24:39 -08:00
parent 3220014605
commit 6b57d7d05d
4 changed files with 94 additions and 11 deletions

View File

@ -214,6 +214,7 @@ class Wormhole:
self.verifier = None
self._sent_data = set() # phases
self._got_data = set()
self._got_confirmation = False
self._closed = False
def __enter__(self):
@ -355,10 +356,20 @@ class Wormhole:
if self._channel is None: raise UsageError
self._got_data.add(phase)
self._get_key()
data_key = self.derive_key(u"wormhole:phase:%s" % phase)
inbound_encrypted = self._channel.get(phase)
phases = []
if not self._got_confirmation:
phases.append(u"_confirm")
phases.append(phase)
(got_phase, body) = self._channel.get_first_of(phases)
if got_phase == u"_confirm":
if body != self.derive_key(u"wormhole:confirmation"):
raise WrongPasswordError
self._got_confirmation = True
(got_phase, body) = self._channel.get_first_of([phase])
assert got_phase == phase
try:
inbound_data = self._decrypt_data(data_key, inbound_encrypted)
data_key = self.derive_key(u"wormhole:phase:%s" % phase)
inbound_data = self._decrypt_data(data_key, body)
return inbound_data
except CryptoError:
raise WrongPasswordError

View File

@ -3,7 +3,8 @@ import json
from twisted.trial import unittest
from twisted.internet.defer import gatherResults, succeed
from twisted.internet.threads import deferToThread
from ..blocking.transcribe import Wormhole, UsageError, ChannelManager
from ..blocking.transcribe import (Wormhole, UsageError, ChannelManager,
WrongPasswordError)
from ..blocking.eventsource import EventSourceFollower
from .common import ServerBase
@ -246,6 +247,35 @@ class Blocking(ServerBase, unittest.TestCase):
d.addCallback(_got_2)
return d
def test_wrong_password(self):
w1 = Wormhole(APPID, self.relayurl)
w2 = Wormhole(APPID, self.relayurl)
# make sure we can detect WrongPasswordError even if one side only
# does get_data() and not send_data(), like "wormhole receive" does
d = deferToThread(w1.get_code)
d.addCallback(lambda code: w2.set_code(code+"not"))
# w2 can't throw WrongPasswordError until it sees a CONFIRM message,
# and w1 won't send CONFIRM until it sees a PAKE message, which w2
# won't send until we call get_data. So we need both sides to be
# running at the same time for this test.
def _w1_sends():
w1.send_data(b"data1")
def _w2_gets():
self.assertRaises(WrongPasswordError, w2.get_data)
d.addCallback(lambda _: self.doBoth([_w1_sends], [_w2_gets]))
# and now w1 should have enough information to throw too
d.addCallback(lambda _: deferToThread(self.assertRaises,
WrongPasswordError, w1.get_data))
def _done(_):
# both sides are closed automatically upon error, but it's still
# legal to call .close(), and should be idempotent
return self.doBoth([w1.close], [w2.close])
d.addCallback(_done)
return d
def test_verifier(self):
w1 = Wormhole(APPID, self.relayurl)
w2 = Wormhole(APPID, self.relayurl)

View File

@ -2,7 +2,8 @@ from __future__ import print_function
import sys, json
from twisted.trial import unittest
from twisted.internet.defer import gatherResults, succeed
from ..twisted.transcribe import Wormhole, UsageError, ChannelManager
from ..twisted.transcribe import (Wormhole, UsageError, ChannelManager,
WrongPasswordError)
from ..twisted.eventsource_twisted import EventSourceParser
from .common import ServerBase
@ -229,6 +230,32 @@ class Basic(ServerBase, unittest.TestCase):
d.addCallback(_got_2)
return d
def test_wrong_password(self):
w1 = Wormhole(APPID, self.relayurl)
w2 = Wormhole(APPID, self.relayurl)
d = w1.get_code()
d.addCallback(lambda code: w2.set_code(code+"not"))
# w2 can't throw WrongPasswordError until it sees a CONFIRM message,
# and w1 won't send CONFIRM until it sees a PAKE message, which w2
# won't send until we call get_data. So we need both sides to be
# running at the same time for this test.
def _w1_sends():
return w1.send_data(b"data1")
def _w2_gets():
return self.assertFailure(w2.get_data(), WrongPasswordError)
d.addCallback(lambda _: self.doBoth(_w1_sends(), _w2_gets()))
# and now w1 should have enough information to throw too
d.addCallback(lambda _: self.assertFailure(w1.get_data(),
WrongPasswordError))
def _done(_):
# both sides are closed automatically upon error, but it's still
# legal to call .close(), and should be idempotent
return self.doBoth(w1.close(), w2.close())
d.addCallback(_done)
return d
def test_verifier(self):
w1 = Wormhole(APPID, self.relayurl)
w2 = Wormhole(APPID, self.relayurl)

View File

@ -206,6 +206,7 @@ class Wormhole:
self._started_get_code = False
self._sent_data = set() # phases
self._got_data = set()
self._got_confirmation = False
def _set_side(self, side):
self._side = side
@ -375,16 +376,30 @@ class Wormhole:
self._got_data.add(phase)
d = self._get_key()
def _get(key):
data_key = self.derive_key(u"wormhole:phase:%s" % phase)
d1 = self._channel.get(phase)
def _decrypt(inbound_encrypted):
phases = []
if not self._got_confirmation:
phases.append(u"_confirm")
phases.append(phase)
d1 = self._channel.get_first_of(phases)
def _maybe_got_confirm(phase_and_body):
(got_phase, body) = phase_and_body
if got_phase == u"_confirm":
if body != self.derive_key(u"wormhole:confirmation"):
raise WrongPasswordError
self._got_confirmation = True
return self._channel.get_first_of([phase])
return phase_and_body
d1.addCallback(_maybe_got_confirm)
def _got(phase_and_body):
(got_phase, body) = phase_and_body
assert got_phase == phase
try:
inbound_data = self._decrypt_data(data_key,
inbound_encrypted)
data_key = self.derive_key(u"wormhole:phase:%s" % phase)
inbound_data = self._decrypt_data(data_key, body)
return inbound_data
except CryptoError:
raise WrongPasswordError
d1.addCallback(_decrypt)
d1.addCallback(_got)
return d1
d.addCallback(_get)
return d