[Pkg-privacy-commits] [obfsproxy] 97/353: Add the Extended ORPort implementation.

Ximin Luo infinity0 at moszumanska.debian.org
Sat Aug 22 13:01:45 UTC 2015


This is an automated email from the git hooks/post-receive script.

infinity0 pushed a commit to branch master
in repository obfsproxy.

commit 8a3796c67f067a5d8c86c53342498119a39ffc5a
Author: George Kadianakis <desnacked at riseup.net>
Date:   Mon Dec 3 17:08:57 2012 +0200

    Add the Extended ORPort implementation.
---
 obfsproxy/network/extended_orport.py | 346 +++++++++++++++++++++++++++++++++++
 1 file changed, 346 insertions(+)

diff --git a/obfsproxy/network/extended_orport.py b/obfsproxy/network/extended_orport.py
new file mode 100644
index 0000000..a80c9dd
--- /dev/null
+++ b/obfsproxy/network/extended_orport.py
@@ -0,0 +1,346 @@
+import os
+
+from twisted.internet.protocol import Protocol
+from twisted.internet import reactor
+
+import obfsproxy.common.log as logging
+import obfsproxy.common.serialize as srlz
+import obfsproxy.common.hmac_sha256 as hmac_sha256
+import obfsproxy.common.rand as rand
+
+import obfsproxy.network.network as network
+import obfsproxy.network.buffer as buffer
+
+log = logging.get_obfslogger()
+
+# Authentication states:
+STATE_WAIT_FOR_AUTH_TYPES = 1
+STATE_WAIT_FOR_SERVER_NONCE = 2
+STATE_WAIT_FOR_RESULTS = 3
+STATE_WAIT_FOR_OKAY = 4
+STATE_OPEN = 5
+
+# Authentication protocol parameters:
+AUTH_PROTOCOL_HEADER_LEN = 4
+
+# Safe-cookie authentication parameters:
+AUTH_SERVER_TO_CLIENT_CONST = "ExtORPort authentication server-to-client hash"
+AUTH_CLIENT_TO_SERVER_CONST = "ExtORPort authentication client-to-server hash"
+AUTH_NONCE_LEN = 32
+AUTH_HASH_LEN = 32
+
+# Extended ORPort commands:
+# Transport-to-Bridge
+EXT_OR_CMD_TB_DONE = 0x0000
+EXT_OR_CMD_TB_USERADDR = 0x0001
+
+# Bridge-to-Transport
+EXT_OR_CMD_BT_OKAY = 0x1000
+EXT_OR_CMD_BT_DENY = 0x1001
+EXT_OR_CMD_BT_CONTROL = 0x1002
+
+# Authentication cookie parameters
+AUTH_COOKIE_LEN = 32
+AUTH_COOKIE_HEADER_LEN = 32
+AUTH_COOKIE_FILE_LEN = AUTH_COOKIE_LEN + AUTH_COOKIE_HEADER_LEN
+AUTH_COOKIE_HEADER = "! Extended ORPort Auth Cookie !\x0a"
+
+def _read_auth_cookie(cookie_path):
+    """
+    Read an Extended ORPort authentication cookie from 'cookie_path' and return it.
+    Throw CouldNotReadCookie if we couldn't read the cookie.
+    """
+
+    # Check if file exists.
+    if not os.path.exists(cookie_path):
+        raise CouldNotReadCookie("'%s' doesn't exist" % cookie_path)
+
+    # Check its size and make sure it's correct before opening.
+    auth_cookie_file_size = os.path.getsize(cookie_path)
+    if auth_cookie_file_size != AUTH_COOKIE_FILE_LEN:
+        raise CouldNotReadCookie("Cookie '%s' is the wrong size (%i bytes instead of %d)" % \
+                                     (cookie_path, auth_cookie_file_size, AUTH_COOKIE_FILE_LEN))
+
+    try:
+        with file(cookie_path, 'rb', 0) as f:
+            header = f.read(AUTH_COOKIE_HEADER_LEN) # first 32 bytes are the header
+
+            if header != AUTH_COOKIE_HEADER:
+                raise CouldNotReadCookie("Corrupted cookie file header '%s'." % header)
+
+            return f.read(AUTH_COOKIE_LEN) # nexta 32 bytes should be the cookie.
+
+    except IOError, exc:
+        raise CouldNotReadCookie("Unable to read '%s' (%s)" % (cookie_path, exc))
+
+class ExtORPortProtocol(network.GenericProtocol):
+    """
+    Represents a connection to the Extended ORPort. It begins by
+    completing the Extended ORPort authentication, then sending some
+    Extended ORPort commands, and finally passing application-data
+    like it would do to an ORPort.
+
+    Specifically, after completing the Extended ORPort authentication
+    we send a USERADDR command with the address of our client, and a
+    DONE command to signal that we are done with the Extended ORPort
+    protocol. Then we wait for an OKAY command back from the server to
+    start sending application-data.
+
+    Attributes:
+    state: The protocol state the connections is currently at.
+    ext_orport_addr: The address of the Extended ORPort.
+
+    peer_addr: The address of the client, in the other side of the
+               circuit, that connected to our downstream side.
+    cookie_file: Path to the Extended ORPort authentication cookie.
+    client_nonce: A random nonce used in the Extended ORPort
+                  authentication protocol.
+    client_hash: Our hash which is used to verify our knowledge of the
+                 authentication cookie in the Extended ORPort Authentication
+                 protocol.
+    """
+    def __init__(self, circuit, ext_orport_addr, cookie_file, peer_addr):
+        self.state = STATE_WAIT_FOR_AUTH_TYPES
+        self.name = "ext_%s" % hex(id(self))
+
+        self.ext_orport_addr = ext_orport_addr
+        self.peer_addr = peer_addr
+        self.cookie_file = cookie_file
+
+        self.client_nonce = rand.random_bytes(AUTH_NONCE_LEN)
+        self.client_hash = None
+
+        network.GenericProtocol.__init__(self, circuit)
+
+    def connectionMade(self):
+        pass
+
+    def dataReceived(self, data_rcvd):
+        """
+        We got some data, process it according to our current state.
+        """
+
+        self.buffer.write(data_rcvd)
+
+        if self.state == STATE_WAIT_FOR_AUTH_TYPES:
+            self._get_auth_types()
+
+        if self.state == STATE_WAIT_FOR_SERVER_NONCE:
+            try:
+                self._get_server_nonce_and_hash()
+            except CouldNotReadCookie, err:
+                log.warning("Extended ORPort Cookie Authentication failed: %s" % err)
+                self.close()
+                return
+
+        if self.state == STATE_WAIT_FOR_RESULTS:
+            try:
+                self._get_auth_results()
+            except CouldNotWriteExtCommand:
+                self.close()
+                return
+
+        if self.state == STATE_WAIT_FOR_OKAY:
+            self._get_okay()
+
+        if self.state == STATE_OPEN:
+            # We are done with the Extended ORPort protocol, we now
+            # treat the Extended ORPort as a normal ORPort.
+            if not self.circuit.circuitIsReady():
+                self.circuit.setUpstreamConnection(self)
+            self.circuit.dataReceived(self.buffer, self)
+
+    def _get_auth_types(self):
+        """
+        Read authentication types that the server supports, select
+        one, and send it to the server.
+        """
+
+        if len(self.buffer) < 2:
+            return
+
+        data = self.buffer.peek()
+        if '\x00' not in data: # haven't received EndAuthTypes yet
+            log.debug("%s: Got some auth types data but no EndAuthTypes yet." % self.name)
+            return
+
+        # Drain all data up to (and including) the EndAuthTypes.
+        log.debug("%s: About to drain %d bytes from %d." % \
+                        (self.name, data.index('\x00')+1, len(self.buffer)))
+        data = self.buffer.read(data.index('\x00')+1)
+
+        if '\x01' not in data:
+            log.debug("%s: Could not find supported auth type." % self.name)
+            self.close()
+            return
+
+        # Send back chosen auth type.
+        self.write("\x01") # Static, since we only support auth type '1' atm.
+
+        # Since we are doing the safe-cookie protocol, now send our
+        # nonce.
+        # XXX This will need to be refactored out of this function in
+        # the future, when we have more than one auth types.
+        self.write(self.client_nonce)
+
+        self.state = STATE_WAIT_FOR_SERVER_NONCE
+
+    def _get_server_nonce_and_hash(self):
+        """
+        Get the server's nonce and hash, validate them and send our own hash.
+        """
+
+        if len(self.buffer) < AUTH_HASH_LEN + AUTH_NONCE_LEN:
+            return
+
+        server_hash = self.buffer.read(AUTH_HASH_LEN)
+        server_nonce = self.buffer.read(AUTH_NONCE_LEN)
+        auth_cookie = _read_auth_cookie(self.cookie_file)
+
+        proper_server_hash = hmac_sha256.hmac_sha256_digest(auth_cookie,
+                                                            AUTH_SERVER_TO_CLIENT_CONST + self.client_nonce + server_nonce)
+
+        log.debug("%s: client_nonce: %s\nserver_nonce: %s\nserver_hash: %s\nproper_server_hash: %s\n" % \
+                      (self.name, repr(self.client_nonce), repr(server_nonce), repr(server_hash), repr(proper_server_hash)))
+
+        if proper_server_hash != server_hash:
+            log.warning("%s: Invalid server hash. Authentication failed." % (self.name))
+            self.close()
+            return
+
+        client_hash = hmac_sha256.hmac_sha256_digest(auth_cookie,
+                                                     AUTH_CLIENT_TO_SERVER_CONST + self.client_nonce + server_nonce)
+
+        # Send our hash.
+        self.write(client_hash)
+
+        self.state = STATE_WAIT_FOR_RESULTS
+
+    def _get_auth_results(self):
+        """
+        Get the authentication results. See if the authentication
+        succeeded or failed, and take appropriate actions.
+        """
+        if len(self.buffer) < 1:
+            return
+
+        result = self.buffer.read(1)
+        if result != '\x01':
+            log.warning("%s: Authentication failed (%s)!" % (self.name, repr(result)))
+            self.close()
+            return
+
+        log.debug("%s: Authentication successful!" % self.name)
+
+        # We've finished the authentication protocol. Send the actual
+        # IP address of our client to the Extended ORPort, then signal
+        # that we are done and that we want to start transferring
+        # application-data.
+        self._write_ext_orport_command(EXT_OR_CMD_TB_USERADDR, '%s:%s' % (self.peer_addr.host, self.peer_addr.port))
+        self._write_ext_orport_command(EXT_OR_CMD_TB_DONE, '')
+
+        self.state = STATE_WAIT_FOR_OKAY
+
+    def _get_okay(self):
+        """
+        We've sent a DONE command to the Extended ORPort and we
+        now check if the Extended ORPort liked it or not.
+        XXX abstract more
+        """
+
+        try:
+            cmd, body = self._get_ext_orport_command(self.buffer)
+        except NeedMoreData: # return if we need more data
+            return
+
+        if cmd != EXT_OR_CMD_BT_OKAY:
+            log.warning("%s: Unexpected command received (%d) after sending DONE." % (self.name, cmd))
+            self.close()
+            return
+
+        self.state = STATE_OPEN
+
+    def _get_ext_orport_command(self, buf):
+        """
+        Reads an Extended ORPort command from 'buf'. Returns (command,
+        body) if it was well-formed, where 'command' is the Extended
+        ORPort command type, and 'body' is its body.
+
+        Throws NeedMoreData.
+        """
+        if len(buf) < AUTH_PROTOCOL_HEADER_LEN:
+            raise NeedMoreData("Not enough data for header.")
+
+        header = buf.peek(AUTH_PROTOCOL_HEADER_LEN)
+        cmd = srlz.ntohs(header[:2])
+        bodylen = srlz.ntohs(header[2:4])
+
+        if (bodylen > len(buf) - AUTH_PROTOCOL_HEADER_LEN): # Not all here yet
+            raise NeedMoreData("Not enough data for body.")
+
+        # We have a whole command. Drain the header.
+        buf.drain(4)
+        body = buf.read(bodylen)
+
+        return (cmd, body)
+
+    def _write_ext_orport_command(self, command, body):
+        """
+        Serialize 'command' and 'body' to an Extended ORPort command
+        and send it to the Extended ORPort.
+
+        Throws CouldNotWriteExtCommand
+        """
+        payload = ''
+
+        if len(body) > 65535: # XXX split instead of quitting?
+            log.warning("Obfsproxy was asked to send Extended ORPort command with more than "
+                        "65535 bytes of body. This is not supported by the Extended ORPort "
+                        "protocol. Please file a bug.")
+            raise CouldNotWriteExtCommand("Too large body.")
+        if command > 65535:
+            raise CouldNotWriteExtCommand("Not supported command type.")
+
+        payload += srlz.htons(command)
+        payload += srlz.htons(len(body))
+        payload += body # body might be absent (empty string)
+        self.write(payload)
+
+
+class ExtORPortClientFactory(network.StaticDestinationClientFactory):
+    def __init__(self, circuit, cookie_file, peer_addr):
+        self.circuit = circuit
+        self.peer_addr = peer_addr
+        self.cookie_file = cookie_file
+
+        self.name = "fact_ext_c_%s" % hex(id(self))
+
+    def buildProtocol(self, addr):
+        return ExtORPortProtocol(self.circuit, addr, self.cookie_file, self.peer_addr)
+
+class ExtORPortServerFactory(network.StaticDestinationClientFactory):
+    def __init__(self, ext_or_addrport, ext_or_cookie_file, transport_class):
+        self.ext_or_host = ext_or_addrport[0]
+        self.ext_or_port = int(ext_or_addrport[1])
+        self.cookie_file = ext_or_cookie_file
+
+        self.transport_class = transport_class
+
+        self.name = "fact_ext_s_%s" % hex(id(self))
+
+    def startFactory(self):
+        log.debug("%s: Starting up Extended ORPort server factory." % self.name)
+
+    def buildProtocol(self, addr):
+        log.debug("%s: New connection from %s:%d." % (self.name, addr.host, addr.port))
+
+        circuit = network.Circuit(self.transport_class())
+
+        # XXX instantiates a new factory for each client
+        clientFactory = ExtORPortClientFactory(circuit, self.cookie_file, addr)
+        reactor.connectTCP(self.ext_or_host, self.ext_or_port, clientFactory)
+
+        return network.StaticDestinationProtocol(circuit, 'server', addr)
+
+class CouldNotReadCookie(Exception): pass
+class NeedMoreData(Exception): pass

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-privacy/packages/obfsproxy.git



More information about the Pkg-privacy-commits mailing list