diff --git a/src/wormhole/blocking/transit.py b/src/wormhole/blocking/transit.py index 7ca7916..5a8e0f5 100644 --- a/src/wormhole/blocking/transit.py +++ b/src/wormhole/blocking/transit.py @@ -1,56 +1,17 @@ from __future__ import print_function -import re, time, threading, socket +import time, threading, socket from six.moves import socketserver from binascii import hexlify, unhexlify from nacl.secret import SecretBox from ..util import ipaddrs from ..util.hkdf import HKDF from ..errors import UsageError - -class TransitError(Exception): - pass - -# The beginning of each TCP connection consists of the following handshake -# messages. The sender transmits the same text regardless of whether it is on -# the initiating/connecting end of the TCP connection, or on the -# listening/accepting side. Same for the receiver. -# -# sender -> receiver: transit sender TXID_HEX ready\n\n -# receiver -> sender: transit receiver RXID_HEX ready\n\n -# -# Any deviations from this result in the socket being closed. The handshake -# messages are designed to provoke an invalid response from other sorts of -# servers (HTTP, SMTP, echo). -# -# If the sender is satisfied with the handshake, and this is the first socket -# to complete negotiation, the sender does: -# -# sender -> receiver: go\n -# -# and the next byte on the wire will be from the application. -# -# If this is not the first socket, the sender does: -# -# sender -> receiver: nevermind\n -# -# and closes the socket. - -# So the receiver looks for "transit sender TXID_HEX ready\n\ngo\n" and hangs -# up upon the first wrong byte. The sender lookgs for "transit receiver -# RXID_HEX ready\n\n" and then makes a first/not-first decision about sending -# "go\n" or "nevermind\n"+close(). - -def build_receiver_handshake(key): - hexid = HKDF(key, 32, CTXinfo=b"transit_receiver") - return b"transit receiver "+hexlify(hexid)+b" ready\n\n" - -def build_sender_handshake(key): - hexid = HKDF(key, 32, CTXinfo=b"transit_sender") - return b"transit sender "+hexlify(hexid)+b" ready\n\n" - -def build_relay_handshake(key): - token = HKDF(key, 32, CTXinfo=b"transit_relay_token") - return b"please relay "+hexlify(token)+b"\n" +from ..transit_common import (TransitError, BadHandshake, TransitClosed, + BadNonce, + build_receiver_handshake, + build_sender_handshake, + build_relay_handshake, + parse_hint_tcp) TIMEOUT=15 @@ -60,10 +21,6 @@ TIMEOUT=15 # 4: add relay # 5: accelerate shutdown of losing sockets - -class BadHandshake(Exception): - pass - def send_to(skt, data): sent = 0 while sent < len(data): @@ -87,36 +44,6 @@ def wait_for(skt, expected, description): raise BadHandshake("got %r want %r on %s" % (got, expected, description)) -# The hint format is: TYPE,VALUE= /^([a-zA-Z0-9]+):(.*)$/ . VALUE depends -# upon TYPE, and it can have more colons in it. For TYPE=tcp (the only one -# currently defined), ADDR,PORT = /^(.*):(\d+)$/ , so ADDR can have colons. -# ADDR can be a hostname, ipv4 dotted-quad, or ipv6 colon-hex. If the hint -# publisher wants anonymity, their only hint's ADDR will end in .onion . - -def parse_hint_tcp(hint): - assert isinstance(hint, type(u"")) - # return tuple or None for an unparseable hint - mo = re.search(r'^([a-zA-Z0-9]+):(.*)$', hint) - if not mo: - print("unparseable hint '%s'" % (hint,)) - return None - hint_type = mo.group(1) - if hint_type != "tcp": - print("unknown hint type '%s' in '%s'" % (hint_type, hint)) - return None - hint_value = mo.group(2) - mo = re.search(r'^(.*):(\d+)$', hint_value) - if not mo: - print("unparseable TCP hint '%s'" % (hint,)) - return None - hint_host = mo.group(1) - try: - hint_port = int(mo.group(2)) - except ValueError: - print("non-numeric port in TCP hint '%s'" % (hint,)) - return None - return hint_host, hint_port - def debug(msg): if False: print(msg) @@ -222,12 +149,6 @@ class MyTCPServer(socketserver.TCPServer): t.start() -class TransitClosed(TransitError): - pass - -class BadNonce(TransitError): - pass - class ReceiveBuffer: def __init__(self, skt): self.skt = skt diff --git a/src/wormhole/transit_common.py b/src/wormhole/transit_common.py new file mode 100644 index 0000000..eb18c92 --- /dev/null +++ b/src/wormhole/transit_common.py @@ -0,0 +1,87 @@ +import re +from binascii import hexlify +from .util.hkdf import HKDF + +class TransitError(Exception): + pass + +class BadHandshake(Exception): + pass + +class TransitClosed(TransitError): + pass + +class BadNonce(TransitError): + pass + +# The beginning of each TCP connection consists of the following handshake +# messages. The sender transmits the same text regardless of whether it is on +# the initiating/connecting end of the TCP connection, or on the +# listening/accepting side. Same for the receiver. +# +# sender -> receiver: transit sender TXID_HEX ready\n\n +# receiver -> sender: transit receiver RXID_HEX ready\n\n +# +# Any deviations from this result in the socket being closed. The handshake +# messages are designed to provoke an invalid response from other sorts of +# servers (HTTP, SMTP, echo). +# +# If the sender is satisfied with the handshake, and this is the first socket +# to complete negotiation, the sender does: +# +# sender -> receiver: go\n +# +# and the next byte on the wire will be from the application. +# +# If this is not the first socket, the sender does: +# +# sender -> receiver: nevermind\n +# +# and closes the socket. + +# So the receiver looks for "transit sender TXID_HEX ready\n\ngo\n" and hangs +# up upon the first wrong byte. The sender lookgs for "transit receiver +# RXID_HEX ready\n\n" and then makes a first/not-first decision about sending +# "go\n" or "nevermind\n"+close(). + +def build_receiver_handshake(key): + hexid = HKDF(key, 32, CTXinfo=b"transit_receiver") + return b"transit receiver "+hexlify(hexid)+b" ready\n\n" + +def build_sender_handshake(key): + hexid = HKDF(key, 32, CTXinfo=b"transit_sender") + return b"transit sender "+hexlify(hexid)+b" ready\n\n" + +def build_relay_handshake(key): + token = HKDF(key, 32, CTXinfo=b"transit_relay_token") + return b"please relay "+hexlify(token)+b"\n" + +# The hint format is: TYPE,VALUE= /^([a-zA-Z0-9]+):(.*)$/ . VALUE depends +# upon TYPE, and it can have more colons in it. For TYPE=tcp (the only one +# currently defined), ADDR,PORT = /^(.*):(\d+)$/ , so ADDR can have colons. +# ADDR can be a hostname, ipv4 dotted-quad, or ipv6 colon-hex. If the hint +# publisher wants anonymity, their only hint's ADDR will end in .onion . + +def parse_hint_tcp(hint): + assert isinstance(hint, type(u"")) + # return tuple or None for an unparseable hint + mo = re.search(r'^([a-zA-Z0-9]+):(.*)$', hint) + if not mo: + print("unparseable hint '%s'" % (hint,)) + return None + hint_type = mo.group(1) + if hint_type != "tcp": + print("unknown hint type '%s' in '%s'" % (hint_type, hint)) + return None + hint_value = mo.group(2) + mo = re.search(r'^(.*):(\d+)$', hint_value) + if not mo: + print("unparseable TCP hint '%s'" % (hint,)) + return None + hint_host = mo.group(1) + try: + hint_port = int(mo.group(2)) + except ValueError: + print("non-numeric port in TCP hint '%s'" % (hint,)) + return None + return hint_host, hint_port