appid and derive_key(purpose) are now unicode
This commit is contained in:
parent
9ba7de6e1e
commit
9e1a00cbd9
24
docs/api.md
24
docs/api.md
|
@ -63,7 +63,7 @@ The synchronous+blocking flow looks like this:
|
||||||
from wormhole.blocking.transcribe import Wormhole
|
from wormhole.blocking.transcribe import Wormhole
|
||||||
from wormhole.public_relay import RENDEZVOUS_RELAY
|
from wormhole.public_relay import RENDEZVOUS_RELAY
|
||||||
mydata = b"initiator's data"
|
mydata = b"initiator's data"
|
||||||
i = Wormhole(b"appid", RENDEZVOUS_RELAY)
|
i = Wormhole(u"appid", RENDEZVOUS_RELAY)
|
||||||
code = i.get_code()
|
code = i.get_code()
|
||||||
print("Invitation Code: %s" % code)
|
print("Invitation Code: %s" % code)
|
||||||
i.send_data(mydata)
|
i.send_data(mydata)
|
||||||
|
@ -78,7 +78,7 @@ from wormhole.blocking.transcribe import Wormhole
|
||||||
from wormhole.public_relay import RENDEZVOUS_RELAY
|
from wormhole.public_relay import RENDEZVOUS_RELAY
|
||||||
mydata = b"receiver's data"
|
mydata = b"receiver's data"
|
||||||
code = sys.argv[1]
|
code = sys.argv[1]
|
||||||
r = Wormhole(b"appid", RENDEZVOUS_RELAY)
|
r = Wormhole(u"appid", RENDEZVOUS_RELAY)
|
||||||
r.set_code(code)
|
r.set_code(code)
|
||||||
r.send_data(mydata)
|
r.send_data(mydata)
|
||||||
theirdata = r.get_data()
|
theirdata = r.get_data()
|
||||||
|
@ -95,7 +95,7 @@ from twisted.internet import reactor
|
||||||
from wormhole.public_relay import RENDEZVOUS_RELAY
|
from wormhole.public_relay import RENDEZVOUS_RELAY
|
||||||
from wormhole.twisted.transcribe import Wormhole
|
from wormhole.twisted.transcribe import Wormhole
|
||||||
outbound_message = b"outbound data"
|
outbound_message = b"outbound data"
|
||||||
w1 = Wormhole(b"appid", RENDEZVOUS_RELAY)
|
w1 = Wormhole(u"appid", RENDEZVOUS_RELAY)
|
||||||
d = w1.get_code()
|
d = w1.get_code()
|
||||||
def _got_code(code):
|
def _got_code(code):
|
||||||
print "Invitation Code:", code
|
print "Invitation Code:", code
|
||||||
|
@ -113,7 +113,7 @@ reactor.run()
|
||||||
On the other side, you call `set_code()` instead of waiting for `get_code()`:
|
On the other side, you call `set_code()` instead of waiting for `get_code()`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
w2 = Wormhole(b"appid", RENDEZVOUS_RELAY)
|
w2 = Wormhole(u"appid", RENDEZVOUS_RELAY)
|
||||||
w2.set_code(code)
|
w2.set_code(code)
|
||||||
d = w2.send_data(my_message)
|
d = w2.send_data(my_message)
|
||||||
...
|
...
|
||||||
|
@ -163,12 +163,12 @@ unicode in python3, plain bytes in python2).
|
||||||
## Application Identifier
|
## Application Identifier
|
||||||
|
|
||||||
Applications using this library must provide an "application identifier", a
|
Applications using this library must provide an "application identifier", a
|
||||||
simple bytestring that distinguishes one application from another. To ensure
|
simple string that distinguishes one application from another. To ensure
|
||||||
uniqueness, use a domain name. To use multiple apps for a single domain, just
|
uniqueness, use a domain name. To use multiple apps for a single domain,
|
||||||
use a string like `example.com/app1`. This string must be the same on both
|
append a URL-like slash and path, like `example.com/app1`. This string must
|
||||||
clients, otherwise they will not see each other. The invitation codes are
|
be the same on both clients, otherwise they will not see each other. The
|
||||||
scoped to the app-id. Note that the app-id must be a bytestring, not unicode,
|
invitation codes are scoped to the app-id. Note that the app-id must be
|
||||||
so on python3 use `b"appid"`.
|
unicode, not bytes, so on python2 use `u"appid"`.
|
||||||
|
|
||||||
Distinct app-ids reduce the size of the connection-id numbers. If fewer than
|
Distinct app-ids reduce the size of the connection-id numbers. If fewer than
|
||||||
ten initiators are active for a given app-id, the connection-id will only
|
ten initiators are active for a given app-id, the connection-id will only
|
||||||
|
@ -244,10 +244,8 @@ re-send it if necessary.
|
||||||
All cryptographically-sensitive parameters are passed as bytes ("str" in
|
All cryptographically-sensitive parameters are passed as bytes ("str" in
|
||||||
python2, "bytes" in python3):
|
python2, "bytes" in python3):
|
||||||
|
|
||||||
* application identifier
|
|
||||||
* verifier string
|
* verifier string
|
||||||
* data in/out
|
* data in/out
|
||||||
* derived-key "purpose" string
|
|
||||||
* transit records in/out
|
* transit records in/out
|
||||||
|
|
||||||
Some human-readable parameters are passed as strings: "str" in python2, "str"
|
Some human-readable parameters are passed as strings: "str" in python2, "str"
|
||||||
|
@ -260,6 +258,8 @@ Some human-readable parameters are passed as strings: "str" in python2, "str"
|
||||||
And some are always unicode, in both python2 and python3:
|
And some are always unicode, in both python2 and python3:
|
||||||
|
|
||||||
* relay URL
|
* relay URL
|
||||||
|
* application identifier
|
||||||
|
* derived-key "purpose" string: `w.derive_key(PURPOSE)`
|
||||||
|
|
||||||
## Detailed Example
|
## Detailed Example
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import os, sys, time, re, requests, json
|
import os, sys, time, re, requests, json, unicodedata
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
from spake2 import SPAKE2_Symmetric
|
from spake2 import SPAKE2_Symmetric
|
||||||
from nacl.secret import SecretBox
|
from nacl.secret import SecretBox
|
||||||
|
@ -15,6 +15,9 @@ from ..channel_monitor import monitor
|
||||||
SECOND = 1
|
SECOND = 1
|
||||||
MINUTE = 60*SECOND
|
MINUTE = 60*SECOND
|
||||||
|
|
||||||
|
def to_bytes(u):
|
||||||
|
return unicodedata.normalize("NFC", u).encode("utf-8")
|
||||||
|
|
||||||
# relay URLs are:
|
# relay URLs are:
|
||||||
# GET /list -> {channelids: [INT..]}
|
# GET /list -> {channelids: [INT..]}
|
||||||
# POST /allocate {side: SIDE} -> {channelid: INT}
|
# POST /allocate {side: SIDE} -> {channelid: INT}
|
||||||
|
@ -130,7 +133,7 @@ class Wormhole:
|
||||||
version_warning_displayed = False
|
version_warning_displayed = False
|
||||||
|
|
||||||
def __init__(self, appid, relay_url):
|
def __init__(self, appid, relay_url):
|
||||||
if not isinstance(appid, type(b"")): raise UsageError
|
if not isinstance(appid, type(u"")): raise UsageError
|
||||||
if not isinstance(relay_url, type(u"")): raise UsageError
|
if not isinstance(relay_url, type(u"")): raise UsageError
|
||||||
if not relay_url.endswith(u"/"): raise UsageError
|
if not relay_url.endswith(u"/"): raise UsageError
|
||||||
self._appid = appid
|
self._appid = appid
|
||||||
|
@ -201,12 +204,12 @@ class Wormhole:
|
||||||
def _start(self):
|
def _start(self):
|
||||||
# allocate the rest now too, so it can be serialized
|
# allocate the rest now too, so it can be serialized
|
||||||
self.sp = SPAKE2_Symmetric(self.code.encode("ascii"),
|
self.sp = SPAKE2_Symmetric(self.code.encode("ascii"),
|
||||||
idSymmetric=self._appid)
|
idSymmetric=to_bytes(self._appid))
|
||||||
self.msg1 = self.sp.start()
|
self.msg1 = self.sp.start()
|
||||||
|
|
||||||
def derive_key(self, purpose, length=SecretBox.KEY_SIZE):
|
def derive_key(self, purpose, length=SecretBox.KEY_SIZE):
|
||||||
if not isinstance(purpose, type(b"")): raise UsageError
|
if not isinstance(purpose, type(u"")): raise UsageError
|
||||||
return HKDF(self.key, length, CTXinfo=purpose)
|
return HKDF(self.key, length, CTXinfo=to_bytes(purpose))
|
||||||
|
|
||||||
def _encrypt_data(self, key, data):
|
def _encrypt_data(self, key, data):
|
||||||
assert isinstance(key, type(b"")), type(key)
|
assert isinstance(key, type(b"")), type(key)
|
||||||
|
@ -230,7 +233,7 @@ class Wormhole:
|
||||||
self.channel.send(u"pake", self.msg1)
|
self.channel.send(u"pake", self.msg1)
|
||||||
pake_msg = self.channel.get(u"pake")
|
pake_msg = self.channel.get(u"pake")
|
||||||
self.key = self.sp.finish(pake_msg)
|
self.key = self.sp.finish(pake_msg)
|
||||||
self.verifier = self.derive_key(self._appid+b":Verifier")
|
self.verifier = self.derive_key(self._appid+u":Verifier")
|
||||||
|
|
||||||
def get_verifier(self):
|
def get_verifier(self):
|
||||||
if self.code is None: raise UsageError
|
if self.code is None: raise UsageError
|
||||||
|
@ -248,7 +251,7 @@ class Wormhole:
|
||||||
# nonces to keep the messages distinct, and the Channel automatically
|
# nonces to keep the messages distinct, and the Channel automatically
|
||||||
# ignores reflections.
|
# ignores reflections.
|
||||||
self._get_key()
|
self._get_key()
|
||||||
data_key = self.derive_key(b"data-key")
|
data_key = self.derive_key(u"data-key")
|
||||||
outbound_encrypted = self._encrypt_data(data_key, outbound_data)
|
outbound_encrypted = self._encrypt_data(data_key, outbound_data)
|
||||||
self.channel.send(u"data", outbound_encrypted)
|
self.channel.send(u"data", outbound_encrypted)
|
||||||
|
|
||||||
|
@ -257,7 +260,7 @@ class Wormhole:
|
||||||
if self.code is None: raise UsageError
|
if self.code is None: raise UsageError
|
||||||
if self.channel is None: raise UsageError
|
if self.channel is None: raise UsageError
|
||||||
self._get_key()
|
self._get_key()
|
||||||
data_key = self.derive_key(b"data-key")
|
data_key = self.derive_key(u"data-key")
|
||||||
inbound_encrypted = self.channel.get(u"data")
|
inbound_encrypted = self.channel.get(u"data")
|
||||||
try:
|
try:
|
||||||
inbound_data = self._decrypt_data(data_key, inbound_encrypted)
|
inbound_data = self._decrypt_data(data_key, inbound_encrypted)
|
||||||
|
|
|
@ -2,7 +2,7 @@ from __future__ import print_function
|
||||||
import os, sys, json, binascii, six
|
import os, sys, json, binascii, six
|
||||||
from ..errors import handle_server_error
|
from ..errors import handle_server_error
|
||||||
|
|
||||||
APPID = b"lothar.com/wormhole/text-or-file-xfer"
|
APPID = u"lothar.com/wormhole/text-or-file-xfer"
|
||||||
|
|
||||||
@handle_server_error
|
@handle_server_error
|
||||||
def receive(args):
|
def receive(args):
|
||||||
|
@ -94,7 +94,7 @@ def receive(args):
|
||||||
|
|
||||||
# now receive the rest of the owl
|
# now receive the rest of the owl
|
||||||
tdata = them_d["transit"]
|
tdata = them_d["transit"]
|
||||||
transit_key = w.derive_key(APPID+b"/transit-key")
|
transit_key = w.derive_key(APPID+u"/transit-key")
|
||||||
transit_receiver.set_transit_key(transit_key)
|
transit_receiver.set_transit_key(transit_key)
|
||||||
transit_receiver.add_their_direct_hints(tdata["direct_connection_hints"])
|
transit_receiver.add_their_direct_hints(tdata["direct_connection_hints"])
|
||||||
transit_receiver.add_their_relay_hints(tdata["relay_connection_hints"])
|
transit_receiver.add_their_relay_hints(tdata["relay_connection_hints"])
|
||||||
|
|
|
@ -2,7 +2,7 @@ from __future__ import print_function
|
||||||
import os, sys, json, binascii, six
|
import os, sys, json, binascii, six
|
||||||
from ..errors import handle_server_error
|
from ..errors import handle_server_error
|
||||||
|
|
||||||
APPID = b"lothar.com/wormhole/text-or-file-xfer"
|
APPID = u"lothar.com/wormhole/text-or-file-xfer"
|
||||||
|
|
||||||
@handle_server_error
|
@handle_server_error
|
||||||
def send(args):
|
def send(args):
|
||||||
|
@ -109,7 +109,7 @@ def send(args):
|
||||||
w.close()
|
w.close()
|
||||||
|
|
||||||
tdata = them_phase1["transit"]
|
tdata = them_phase1["transit"]
|
||||||
transit_key = w.derive_key(APPID+b"/transit-key")
|
transit_key = w.derive_key(APPID+"/transit-key")
|
||||||
transit_sender.set_transit_key(transit_key)
|
transit_sender.set_transit_key(transit_key)
|
||||||
transit_sender.add_their_direct_hints(tdata["direct_connection_hints"])
|
transit_sender.add_their_direct_hints(tdata["direct_connection_hints"])
|
||||||
transit_sender.add_their_relay_hints(tdata["relay_connection_hints"])
|
transit_sender.add_their_relay_hints(tdata["relay_connection_hints"])
|
||||||
|
|
|
@ -5,6 +5,8 @@ from twisted.internet.threads import deferToThread
|
||||||
from ..blocking.transcribe import Wormhole as BlockingWormhole, UsageError
|
from ..blocking.transcribe import Wormhole as BlockingWormhole, UsageError
|
||||||
from .common import ServerBase
|
from .common import ServerBase
|
||||||
|
|
||||||
|
APPID = u"appid"
|
||||||
|
|
||||||
class Blocking(ServerBase, unittest.TestCase):
|
class Blocking(ServerBase, unittest.TestCase):
|
||||||
# we need Twisted to run the server, but we run the sender and receiver
|
# we need Twisted to run the server, but we run the sender and receiver
|
||||||
# with deferToThread()
|
# with deferToThread()
|
||||||
|
@ -18,9 +20,8 @@ class Blocking(ServerBase, unittest.TestCase):
|
||||||
deferToThread(f2, *f2args)], True)
|
deferToThread(f2, *f2args)], True)
|
||||||
|
|
||||||
def test_basic(self):
|
def test_basic(self):
|
||||||
appid = b"appid"
|
w1 = BlockingWormhole(APPID, self.relayurl)
|
||||||
w1 = BlockingWormhole(appid, self.relayurl)
|
w2 = BlockingWormhole(APPID, self.relayurl)
|
||||||
w2 = BlockingWormhole(appid, self.relayurl)
|
|
||||||
d = deferToThread(w1.get_code)
|
d = deferToThread(w1.get_code)
|
||||||
def _got_code(code):
|
def _got_code(code):
|
||||||
w2.set_code(code)
|
w2.set_code(code)
|
||||||
|
@ -39,9 +40,8 @@ class Blocking(ServerBase, unittest.TestCase):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_interleaved(self):
|
def test_interleaved(self):
|
||||||
appid = b"appid"
|
w1 = BlockingWormhole(APPID, self.relayurl)
|
||||||
w1 = BlockingWormhole(appid, self.relayurl)
|
w2 = BlockingWormhole(APPID, self.relayurl)
|
||||||
w2 = BlockingWormhole(appid, self.relayurl)
|
|
||||||
d = deferToThread(w1.get_code)
|
d = deferToThread(w1.get_code)
|
||||||
def _got_code(code):
|
def _got_code(code):
|
||||||
w2.set_code(code)
|
w2.set_code(code)
|
||||||
|
@ -61,9 +61,8 @@ class Blocking(ServerBase, unittest.TestCase):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_fixed_code(self):
|
def test_fixed_code(self):
|
||||||
appid = b"appid"
|
w1 = BlockingWormhole(APPID, self.relayurl)
|
||||||
w1 = BlockingWormhole(appid, self.relayurl)
|
w2 = BlockingWormhole(APPID, self.relayurl)
|
||||||
w2 = BlockingWormhole(appid, self.relayurl)
|
|
||||||
w1.set_code("123-purple-elephant")
|
w1.set_code("123-purple-elephant")
|
||||||
w2.set_code("123-purple-elephant")
|
w2.set_code("123-purple-elephant")
|
||||||
d = self.doBoth([w1.send_data, b"data1"], [w2.send_data, b"data2"])
|
d = self.doBoth([w1.send_data, b"data1"], [w2.send_data, b"data2"])
|
||||||
|
@ -79,9 +78,8 @@ class Blocking(ServerBase, unittest.TestCase):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_verifier(self):
|
def test_verifier(self):
|
||||||
appid = b"appid"
|
w1 = BlockingWormhole(APPID, self.relayurl)
|
||||||
w1 = BlockingWormhole(appid, self.relayurl)
|
w2 = BlockingWormhole(APPID, self.relayurl)
|
||||||
w2 = BlockingWormhole(appid, self.relayurl)
|
|
||||||
d = deferToThread(w1.get_code)
|
d = deferToThread(w1.get_code)
|
||||||
def _got_code(code):
|
def _got_code(code):
|
||||||
w2.set_code(code)
|
w2.set_code(code)
|
||||||
|
@ -106,9 +104,8 @@ class Blocking(ServerBase, unittest.TestCase):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_verifier_mismatch(self):
|
def test_verifier_mismatch(self):
|
||||||
appid = b"appid"
|
w1 = BlockingWormhole(APPID, self.relayurl)
|
||||||
w1 = BlockingWormhole(appid, self.relayurl)
|
w2 = BlockingWormhole(APPID, self.relayurl)
|
||||||
w2 = BlockingWormhole(appid, self.relayurl)
|
|
||||||
d = deferToThread(w1.get_code)
|
d = deferToThread(w1.get_code)
|
||||||
def _got_code(code):
|
def _got_code(code):
|
||||||
w2.set_code(code+"not")
|
w2.set_code(code+"not")
|
||||||
|
@ -123,15 +120,14 @@ class Blocking(ServerBase, unittest.TestCase):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_errors(self):
|
def test_errors(self):
|
||||||
appid = b"appid"
|
w1 = BlockingWormhole(APPID, self.relayurl)
|
||||||
w1 = BlockingWormhole(appid, self.relayurl)
|
|
||||||
self.assertRaises(UsageError, w1.get_verifier)
|
self.assertRaises(UsageError, w1.get_verifier)
|
||||||
self.assertRaises(UsageError, w1.get_data)
|
self.assertRaises(UsageError, w1.get_data)
|
||||||
self.assertRaises(UsageError, w1.send_data, b"data")
|
self.assertRaises(UsageError, w1.send_data, b"data")
|
||||||
w1.set_code("123-purple-elephant")
|
w1.set_code("123-purple-elephant")
|
||||||
self.assertRaises(UsageError, w1.set_code, "123-nope")
|
self.assertRaises(UsageError, w1.set_code, "123-nope")
|
||||||
self.assertRaises(UsageError, w1.get_code)
|
self.assertRaises(UsageError, w1.get_code)
|
||||||
w2 = BlockingWormhole(appid, self.relayurl)
|
w2 = BlockingWormhole(APPID, self.relayurl)
|
||||||
d = deferToThread(w2.get_code)
|
d = deferToThread(w2.get_code)
|
||||||
def _done(code):
|
def _done(code):
|
||||||
self.assertRaises(UsageError, w2.get_code)
|
self.assertRaises(UsageError, w2.get_code)
|
||||||
|
@ -140,10 +136,9 @@ class Blocking(ServerBase, unittest.TestCase):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_serialize(self):
|
def test_serialize(self):
|
||||||
appid = b"appid"
|
w1 = BlockingWormhole(APPID, self.relayurl)
|
||||||
w1 = BlockingWormhole(appid, self.relayurl)
|
|
||||||
self.assertRaises(UsageError, w1.serialize) # too early
|
self.assertRaises(UsageError, w1.serialize) # too early
|
||||||
w2 = BlockingWormhole(appid, self.relayurl)
|
w2 = BlockingWormhole(APPID, self.relayurl)
|
||||||
d = deferToThread(w1.get_code)
|
d = deferToThread(w1.get_code)
|
||||||
def _got_code(code):
|
def _got_code(code):
|
||||||
self.assertRaises(UsageError, w2.serialize) # too early
|
self.assertRaises(UsageError, w2.serialize) # too early
|
||||||
|
|
|
@ -4,15 +4,16 @@ from twisted.internet.defer import gatherResults
|
||||||
from ..twisted.transcribe import Wormhole, UsageError
|
from ..twisted.transcribe import Wormhole, UsageError
|
||||||
from .common import ServerBase
|
from .common import ServerBase
|
||||||
|
|
||||||
|
APPID = u"appid"
|
||||||
|
|
||||||
class Basic(ServerBase, unittest.TestCase):
|
class Basic(ServerBase, unittest.TestCase):
|
||||||
|
|
||||||
def doBoth(self, d1, d2):
|
def doBoth(self, d1, d2):
|
||||||
return gatherResults([d1, d2], True)
|
return gatherResults([d1, d2], True)
|
||||||
|
|
||||||
def test_basic(self):
|
def test_basic(self):
|
||||||
appid = b"appid"
|
w1 = Wormhole(APPID, self.relayurl)
|
||||||
w1 = Wormhole(appid, self.relayurl)
|
w2 = Wormhole(APPID, self.relayurl)
|
||||||
w2 = Wormhole(appid, self.relayurl)
|
|
||||||
d = w1.get_code()
|
d = w1.get_code()
|
||||||
def _got_code(code):
|
def _got_code(code):
|
||||||
w2.set_code(code)
|
w2.set_code(code)
|
||||||
|
@ -30,9 +31,8 @@ class Basic(ServerBase, unittest.TestCase):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_interleaved(self):
|
def test_interleaved(self):
|
||||||
appid = b"appid"
|
w1 = Wormhole(APPID, self.relayurl)
|
||||||
w1 = Wormhole(appid, self.relayurl)
|
w2 = Wormhole(APPID, self.relayurl)
|
||||||
w2 = Wormhole(appid, self.relayurl)
|
|
||||||
d = w1.get_code()
|
d = w1.get_code()
|
||||||
def _got_code(code):
|
def _got_code(code):
|
||||||
w2.set_code(code)
|
w2.set_code(code)
|
||||||
|
@ -51,9 +51,8 @@ class Basic(ServerBase, unittest.TestCase):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_fixed_code(self):
|
def test_fixed_code(self):
|
||||||
appid = b"appid"
|
w1 = Wormhole(APPID, self.relayurl)
|
||||||
w1 = Wormhole(appid, self.relayurl)
|
w2 = Wormhole(APPID, self.relayurl)
|
||||||
w2 = Wormhole(appid, self.relayurl)
|
|
||||||
w1.set_code("123-purple-elephant")
|
w1.set_code("123-purple-elephant")
|
||||||
w2.set_code("123-purple-elephant")
|
w2.set_code("123-purple-elephant")
|
||||||
d = self.doBoth(w1.send_data(b"data1"), w2.send_data(b"data2"))
|
d = self.doBoth(w1.send_data(b"data1"), w2.send_data(b"data2"))
|
||||||
|
@ -69,9 +68,8 @@ class Basic(ServerBase, unittest.TestCase):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_verifier(self):
|
def test_verifier(self):
|
||||||
appid = b"appid"
|
w1 = Wormhole(APPID, self.relayurl)
|
||||||
w1 = Wormhole(appid, self.relayurl)
|
w2 = Wormhole(APPID, self.relayurl)
|
||||||
w2 = Wormhole(appid, self.relayurl)
|
|
||||||
d = w1.get_code()
|
d = w1.get_code()
|
||||||
def _got_code(code):
|
def _got_code(code):
|
||||||
w2.set_code(code)
|
w2.set_code(code)
|
||||||
|
@ -95,9 +93,8 @@ class Basic(ServerBase, unittest.TestCase):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_verifier_mismatch(self):
|
def test_verifier_mismatch(self):
|
||||||
appid = b"appid"
|
w1 = Wormhole(APPID, self.relayurl)
|
||||||
w1 = Wormhole(appid, self.relayurl)
|
w2 = Wormhole(APPID, self.relayurl)
|
||||||
w2 = Wormhole(appid, self.relayurl)
|
|
||||||
d = w1.get_code()
|
d = w1.get_code()
|
||||||
def _got_code(code):
|
def _got_code(code):
|
||||||
w2.set_code(code+"not")
|
w2.set_code(code+"not")
|
||||||
|
@ -112,15 +109,14 @@ class Basic(ServerBase, unittest.TestCase):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_errors(self):
|
def test_errors(self):
|
||||||
appid = b"appid"
|
w1 = Wormhole(APPID, self.relayurl)
|
||||||
w1 = Wormhole(appid, self.relayurl)
|
|
||||||
self.assertRaises(UsageError, w1.get_verifier)
|
self.assertRaises(UsageError, w1.get_verifier)
|
||||||
self.assertRaises(UsageError, w1.send_data, b"data")
|
self.assertRaises(UsageError, w1.send_data, b"data")
|
||||||
self.assertRaises(UsageError, w1.get_data)
|
self.assertRaises(UsageError, w1.get_data)
|
||||||
w1.set_code("123-purple-elephant")
|
w1.set_code("123-purple-elephant")
|
||||||
self.assertRaises(UsageError, w1.set_code, "123-nope")
|
self.assertRaises(UsageError, w1.set_code, "123-nope")
|
||||||
self.assertRaises(UsageError, w1.get_code)
|
self.assertRaises(UsageError, w1.get_code)
|
||||||
w2 = Wormhole(appid, self.relayurl)
|
w2 = Wormhole(APPID, self.relayurl)
|
||||||
d = w2.get_code()
|
d = w2.get_code()
|
||||||
self.assertRaises(UsageError, w2.get_code)
|
self.assertRaises(UsageError, w2.get_code)
|
||||||
def _got_code(code):
|
def _got_code(code):
|
||||||
|
@ -129,10 +125,9 @@ class Basic(ServerBase, unittest.TestCase):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_serialize(self):
|
def test_serialize(self):
|
||||||
appid = b"appid"
|
w1 = Wormhole(APPID, self.relayurl)
|
||||||
w1 = Wormhole(appid, self.relayurl)
|
|
||||||
self.assertRaises(UsageError, w1.serialize) # too early
|
self.assertRaises(UsageError, w1.serialize) # too early
|
||||||
w2 = Wormhole(appid, self.relayurl)
|
w2 = Wormhole(APPID, self.relayurl)
|
||||||
d = w1.get_code()
|
d = w1.get_code()
|
||||||
def _got_code(code):
|
def _got_code(code):
|
||||||
self.assertRaises(UsageError, w2.serialize) # too early
|
self.assertRaises(UsageError, w2.serialize) # too early
|
||||||
|
|
|
@ -5,7 +5,7 @@ from twisted.internet import reactor
|
||||||
from .transcribe import Wormhole
|
from .transcribe import Wormhole
|
||||||
from .. import public_relay
|
from .. import public_relay
|
||||||
|
|
||||||
APPID = b"lothar.com/wormhole/text-or-file-xfer"
|
APPID = u"lothar.com/wormhole/text-or-file-xfer"
|
||||||
|
|
||||||
w = Wormhole(APPID, public_relay.RENDEZVOUS_RELAY)
|
w = Wormhole(APPID, public_relay.RENDEZVOUS_RELAY)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import os, sys, json, re
|
import os, sys, json, re, unicodedata
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
from twisted.internet import reactor, defer
|
from twisted.internet import reactor, defer
|
||||||
|
@ -17,6 +17,9 @@ from ..errors import ServerError, WrongPasswordError, UsageError
|
||||||
from ..util.hkdf import HKDF
|
from ..util.hkdf import HKDF
|
||||||
from ..channel_monitor import monitor
|
from ..channel_monitor import monitor
|
||||||
|
|
||||||
|
def to_bytes(u):
|
||||||
|
return unicodedata.normalize("NFC", u).encode("utf-8")
|
||||||
|
|
||||||
@implementer(IBodyProducer)
|
@implementer(IBodyProducer)
|
||||||
class DataProducer:
|
class DataProducer:
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
|
@ -146,7 +149,7 @@ class Wormhole:
|
||||||
version_warning_displayed = False
|
version_warning_displayed = False
|
||||||
|
|
||||||
def __init__(self, appid, relay_url):
|
def __init__(self, appid, relay_url):
|
||||||
if not isinstance(appid, type(b"")): raise UsageError
|
if not isinstance(appid, type(u"")): raise UsageError
|
||||||
if not isinstance(relay_url, type(u"")): raise UsageError
|
if not isinstance(relay_url, type(u"")): raise UsageError
|
||||||
if not relay_url.endswith(u"/"): raise UsageError
|
if not relay_url.endswith(u"/"): raise UsageError
|
||||||
self._appid = appid
|
self._appid = appid
|
||||||
|
@ -219,7 +222,7 @@ class Wormhole:
|
||||||
def _start(self):
|
def _start(self):
|
||||||
# allocate the rest now too, so it can be serialized
|
# allocate the rest now too, so it can be serialized
|
||||||
self.sp = SPAKE2_Symmetric(self.code.encode("ascii"),
|
self.sp = SPAKE2_Symmetric(self.code.encode("ascii"),
|
||||||
idSymmetric=self._appid)
|
idSymmetric=to_bytes(self._appid))
|
||||||
self.msg1 = self.sp.start()
|
self.msg1 = self.sp.start()
|
||||||
|
|
||||||
def serialize(self):
|
def serialize(self):
|
||||||
|
@ -242,7 +245,7 @@ class Wormhole:
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_serialized(klass, data):
|
def from_serialized(klass, data):
|
||||||
d = json.loads(data)
|
d = json.loads(data)
|
||||||
self = klass(d["appid"].encode("ascii"), d["relay_url"])
|
self = klass(d["appid"], d["relay_url"])
|
||||||
self._set_side(d["side"].encode("ascii"))
|
self._set_side(d["side"].encode("ascii"))
|
||||||
self._set_code_and_channelid(d["code"].encode("ascii"))
|
self._set_code_and_channelid(d["code"].encode("ascii"))
|
||||||
self.sp = SPAKE2_Symmetric.from_serialized(json.dumps(d["spake2"]))
|
self.sp = SPAKE2_Symmetric.from_serialized(json.dumps(d["spake2"]))
|
||||||
|
@ -250,11 +253,11 @@ class Wormhole:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def derive_key(self, purpose, length=SecretBox.KEY_SIZE):
|
def derive_key(self, purpose, length=SecretBox.KEY_SIZE):
|
||||||
|
if not isinstance(purpose, type(u"")): raise UsageError
|
||||||
if self.key is None:
|
if self.key is None:
|
||||||
# call after get_verifier() or get_data()
|
# call after get_verifier() or get_data()
|
||||||
raise UsageError
|
raise UsageError
|
||||||
if not isinstance(purpose, type(b"")): raise UsageError
|
return HKDF(self.key, length, CTXinfo=to_bytes(purpose))
|
||||||
return HKDF(self.key, length, CTXinfo=purpose)
|
|
||||||
|
|
||||||
def _encrypt_data(self, key, data):
|
def _encrypt_data(self, key, data):
|
||||||
assert isinstance(key, type(b"")), type(key)
|
assert isinstance(key, type(b"")), type(key)
|
||||||
|
@ -282,7 +285,7 @@ class Wormhole:
|
||||||
def _got_pake(pake_msg):
|
def _got_pake(pake_msg):
|
||||||
key = self.sp.finish(pake_msg)
|
key = self.sp.finish(pake_msg)
|
||||||
self.key = key
|
self.key = key
|
||||||
self.verifier = self.derive_key(self._appid+b":Verifier")
|
self.verifier = self.derive_key(self._appid+u":Verifier")
|
||||||
return key
|
return key
|
||||||
d.addCallback(_got_pake)
|
d.addCallback(_got_pake)
|
||||||
return d
|
return d
|
||||||
|
@ -304,7 +307,7 @@ class Wormhole:
|
||||||
# ignores reflections.
|
# ignores reflections.
|
||||||
d = self._get_key()
|
d = self._get_key()
|
||||||
def _send(key):
|
def _send(key):
|
||||||
data_key = self.derive_key(b"data-key")
|
data_key = self.derive_key(u"data-key")
|
||||||
outbound_encrypted = self._encrypt_data(data_key, outbound_data)
|
outbound_encrypted = self._encrypt_data(data_key, outbound_data)
|
||||||
return self.channel.send(u"data", outbound_encrypted)
|
return self.channel.send(u"data", outbound_encrypted)
|
||||||
d.addCallback(_send)
|
d.addCallback(_send)
|
||||||
|
@ -316,7 +319,7 @@ class Wormhole:
|
||||||
if self.channel is None: raise UsageError
|
if self.channel is None: raise UsageError
|
||||||
d = self._get_key()
|
d = self._get_key()
|
||||||
def _get(key):
|
def _get(key):
|
||||||
data_key = self.derive_key(b"data-key")
|
data_key = self.derive_key(u"data-key")
|
||||||
d1 = self.channel.get(u"data")
|
d1 = self.channel.get(u"data")
|
||||||
def _decrypt(inbound_encrypted):
|
def _decrypt(inbound_encrypted):
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user