diff --git a/src/wormhole/cli/runner.py b/src/wormhole/cli/runner.py index ac984c1..8c373b1 100644 --- a/src/wormhole/cli/runner.py +++ b/src/wormhole/cli/runner.py @@ -4,7 +4,8 @@ start = time.time() import os, sys, textwrap from twisted.internet.defer import maybeDeferred from twisted.internet.task import react -from ..errors import TransferError, WrongPasswordError, WelcomeError, Timeout +from ..errors import (TransferError, WrongPasswordError, WelcomeError, Timeout, + KeyFormatError) from ..timing import DebugTiming from .cli_args import parser top_import_finish = time.time() @@ -49,7 +50,8 @@ def run(reactor, argv, cwd, stdout, stderr, executable=None): d.addBoth(_maybe_dump_timing) def _explain_error(f): # these errors don't print a traceback, just an explanation - f.trap(TransferError, WrongPasswordError, WelcomeError, Timeout) + f.trap(TransferError, WrongPasswordError, WelcomeError, Timeout, + KeyFormatError) if f.check(WrongPasswordError): msg = textwrap.fill("ERROR: " + textwrap.dedent(f.value.__doc__)) print(msg, file=stderr) @@ -58,6 +60,9 @@ def run(reactor, argv, cwd, stdout, stderr, executable=None): print(msg, file=stderr) print(file=stderr) print(str(f.value), file=stderr) + elif f.check(KeyFormatError): + msg = textwrap.fill("ERROR: " + textwrap.dedent(f.value.__doc__)) + print(msg, file=stderr) else: print("ERROR:", f.value, file=stderr) raise SystemExit(1) diff --git a/src/wormhole/errors.py b/src/wormhole/errors.py index 08ebae7..47b9c2e 100644 --- a/src/wormhole/errors.py +++ b/src/wormhole/errors.py @@ -36,6 +36,13 @@ class WrongPasswordError(Exception): # or the data blob was corrupted, and that's why decrypt failed pass +class KeyFormatError(Exception): + """ + The key you entered contains spaces. Magic-wormhole expects keys to be + separated by dashes. Please reenter the key you were given separating the + words with dashes. + """ + class ReflectionAttack(Exception): """An attacker (or bug) reflected our outgoing message back to us.""" diff --git a/src/wormhole/test/test_wormhole.py b/src/wormhole/test/test_wormhole.py index ba54123..795b6b6 100644 --- a/src/wormhole/test/test_wormhole.py +++ b/src/wormhole/test/test_wormhole.py @@ -7,7 +7,8 @@ from twisted.internet import reactor from twisted.internet.defer import Deferred, gatherResults, inlineCallbacks from .common import ServerBase from .. import wormhole -from ..errors import WrongPasswordError, WelcomeError, UsageError +from ..errors import (WrongPasswordError, WelcomeError, UsageError, + KeyFormatError) from spake2 import SPAKE2_Symmetric from ..timing import DebugTiming from ..util import (bytes_to_dict, dict_to_bytes, @@ -818,6 +819,23 @@ class Wormholes(ServerBase, unittest.TestCase): yield w2.close() self.flushLoggedErrors(WrongPasswordError) + @inlineCallbacks + def test_wrong_password_with_spaces(self): + w1 = wormhole.wormhole(APPID, self.relayurl, reactor) + w2 = wormhole.wormhole(APPID, self.relayurl, reactor) + code = yield w1.get_code() + code_no_dashes = code.replace('-', ' ') + + with self.assertRaises(KeyFormatError) as ex: + w2.set_code(code_no_dashes) + + expected_msg = "code (%s) contains spaces." % (code_no_dashes,) + self.assertEqual(expected_msg, str(ex.exception)) + + yield w1.close() + yield w2.close() + self.flushLoggedErrors(KeyFormatError) + @inlineCallbacks def test_verifier(self): w1 = wormhole.wormhole(APPID, self.relayurl, reactor) @@ -875,4 +893,3 @@ class Errors(ServerBase, unittest.TestCase): yield self.assertFailure(w.get_code(), UsageError) yield self.assertFailure(w.input_code(), UsageError) yield w.close() - diff --git a/src/wormhole/wormhole.py b/src/wormhole/wormhole.py index fafb966..7392433 100644 --- a/src/wormhole/wormhole.py +++ b/src/wormhole/wormhole.py @@ -15,7 +15,7 @@ from . import __version__ from . import codes #from .errors import ServerError, Timeout from .errors import (WrongPasswordError, UsageError, WelcomeError, - WormholeClosedError) + WormholeClosedError, KeyFormatError) from .timing import DebugTiming from .util import (to_bytes, bytes_to_hexstr, hexstr_to_bytes, dict_to_bytes, bytes_to_dict) @@ -476,6 +476,10 @@ class _Wormhole: def _event_learned_code(self, code): self._timing.add("code established") + # bail out early if the password contains spaces... + # this should raise a useful error + if ' ' in code: + raise KeyFormatError("code (%s) contains spaces." % code) self._code = code mo = re.search(r'^(\d+)-', code) if not mo: