[Pkg-privacy-commits] [obfsproxy] 101/353: Implement the obfs3 pluggable transport.

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 86d4f61ea5c3a6a84ae731a16dbe29a9a32bfe01
Author: George Kadianakis <desnacked at riseup.net>
Date:   Tue Jan 22 19:51:22 2013 +0200

    Implement the obfs3 pluggable transport.
---
 obfsproxy/network/network.py       |  12 +-
 obfsproxy/transports/obfs3.py      | 224 +++++++++++++++++++++++++++++++++++++
 obfsproxy/transports/transports.py |   4 +-
 3 files changed, 236 insertions(+), 4 deletions(-)

diff --git a/obfsproxy/network/network.py b/obfsproxy/network/network.py
index 5d693bf..2116eb5 100644
--- a/obfsproxy/network/network.py
+++ b/obfsproxy/network/network.py
@@ -120,14 +120,20 @@ class Circuit(Protocol):
         connected. Do all the things we have to do now.
         """
 
-        # Do a dummy dataReceived on the initiating connection in case
-        # it has any buffered data that must be flushed to the network.
-        conn_to_flush.dataReceived('') # XXX hack way to flush
+        log.debug("%s: Circuit completed." % self.name)
 
         # Call the transport-specific handshake method since this is a
         # good time to perform a handshake.
         self.transport.handshake(self)
 
+        # Do a dummy dataReceived on the initiating connection in case
+        # it has any buffered data that must be flushed to the network.
+        #
+        # (We use callLater because we want to return back to the
+        # event loop so that our handshake() messages get sent to the
+        # network immediately.)
+        reactor.callLater(0.01, conn_to_flush.dataReceived, '')
+
     def dataReceived(self, data, conn):
         """
         We received 'data' on 'conn'. Pass the data to our transport,
diff --git a/obfsproxy/transports/obfs3.py b/obfsproxy/transports/obfs3.py
new file mode 100644
index 0000000..88390e9
--- /dev/null
+++ b/obfsproxy/transports/obfs3.py
@@ -0,0 +1,224 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+The obfs3 module implements the obfs3 protocol.
+"""
+
+import random
+
+import obfsproxy.common.aes as aes
+import obfsproxy.transports.base as base
+import obfsproxy.transports.obfs3_dh as obfs3_dh
+import obfsproxy.common.log as logging
+import obfsproxy.common.hmac_sha256 as hmac_sha256
+import obfsproxy.common.rand as rand
+
+log = logging.get_obfslogger()
+
+MAX_PADDING = 8194
+
+PUBKEY_LEN = 192
+KEYLEN = 16  # is the length of the key used by E(K,s) -- that is, 16.
+IVLEN = 16  # is the length of the IV used by E(K,s) -- that is, 16.
+
+ST_WAIT_FOR_KEY = 0 # Waiting for public key from the other party
+ST_SEARCHING_MAGIC = 1 # Waiting for magic strings from the other party
+ST_OPEN = 2 # obfs3 handshake is complete. Sending application data.
+
+class Obfs3Transport(base.BaseTransport):
+    """
+    Obfs3Transport implements the obfs3 protocol.
+    """
+
+    def __init__(self):
+        """Initialize the obfs3 pluggable transport."""
+
+        # Our state.
+        self.state = ST_WAIT_FOR_KEY
+
+        # Uniform-DH object
+        self.dh = obfs3_dh.UniformDH()
+
+        # DH shared secret
+        self.shared_secret = None
+
+        # Bytes of padding scanned so far.
+        self.scanned_padding = 0
+        # Last padding bytes scanned.
+        self.last_padding_chunk = ''
+
+        # Magic value that the other party is going to send
+        # (initialized after deriving shared secret)
+        self.other_magic_value = None
+        # Crypto to encrypt outgoing data.
+        self.send_crypto = None
+        # Crypto to decrypt incoming data.
+        self.recv_crypto = None
+
+        # Buffer for the first data, Tor is trying to send but can't right now
+        # because we have to handle the DH handshake first.
+        self.queued_data = ''
+
+        # Attributes below are filled by classes that inherit Obfs3Transport.
+        self.send_keytype = None
+        self.recv_keytype = None
+        self.send_magic_const = None
+        self.recv_magic_const = None
+        self.we_are_initiator = None
+
+    def handshake(self, circuit):
+        """
+        Do the obfs3 handshake:
+        PUBKEY | WR(PADLEN)
+        """
+        padding_length = random.randint(0, MAX_PADDING/2)
+
+        handshake_message = self.dh.get_public() + rand.random_bytes(padding_length)
+
+        log.debug("obfs3 handshake: %s queued %d bytes (padding_length: %d) (public key: %s).",
+                  "initiator" if self.we_are_initiator else "responder",
+                  len(handshake_message), padding_length, repr(self.dh.get_public()))
+
+        circuit.downstream.write(handshake_message)
+
+    def receivedUpstream(self, data, circuit):
+        """
+        Got data from upstream. We need to obfuscated and proxy them downstream.
+        """
+        if not self.send_crypto:
+            log.debug("Got upstream data before doing handshake. Caching.")
+            self.queued_data += data.read()
+            return
+
+        message = self.send_crypto.crypt(data.read())
+        log.debug("obfs3 receivedUpstream: Transmitting %d bytes.", len(message))
+
+        # Proxy encrypted message.
+        circuit.downstream.write(message)
+
+    def receivedDownstream(self, data, circuit):
+        """
+        Got data from downstream. We need to de-obfuscate them and
+        proxy them upstream.
+        """
+
+        if self.state == ST_WAIT_FOR_KEY: # Looking for the other peer's pubkey
+            self._read_handshake(data, circuit)
+
+        if self.state == ST_SEARCHING_MAGIC: # Looking for the magic string
+            self._scan_for_magic(data)
+
+        if self.state == ST_OPEN: # Handshake is done. Just decrypt and read application data.
+            log.debug("obfs3 receivedDownstream: Processing %d bytes of application data." %
+                      len(data))
+            circuit.upstream.write(self.recv_crypto.crypt(data.read()))
+
+    def _read_handshake(self, data, circuit):
+        """
+        Read handshake message, parse the other peer's public key and
+        set up our crypto.
+        """
+
+        log_prefix = "obfs3:_read_handshake()"
+        if len(data) < PUBKEY_LEN:
+            log.debug("%s: Not enough bytes for key (%d)." % (log_prefix, len(data)))
+            return
+
+        log.debug("%s: Got %d bytes of handshake data (waiting for key)." % (log_prefix, len(data)))
+
+        # Get the public key from the handshake message, do the DH and
+        # get the shared secret.
+        other_pubkey = data.read(PUBKEY_LEN)
+        try:
+            self.shared_secret = self.dh.get_secret(other_pubkey)
+        except ValueError:
+            raise base.PluggableTransportError("obfs3: Corrupted public key '%s'" % repr(other_pubkey))
+        log.debug("Got public key: %s.\nGot shared secret: %s" %
+                  (repr(other_pubkey), repr(self.shared_secret)))
+
+        # Set up our crypto.
+        self.send_crypto = self._derive_crypto(self.send_keytype)
+        self.recv_crypto = self._derive_crypto(self.recv_keytype)
+        self.other_magic_value = hmac_sha256.hmac_sha256_digest(self.shared_secret,
+                                                                self.recv_magic_const)
+
+        # Send our magic value to the remote end and append the queued outgoing data.
+        # Padding is prepended so that the server does not just send the 32-byte magic
+        # in a single TCP segment.
+        padding_length = random.randint(0, MAX_PADDING/2)
+        magic = hmac_sha256.hmac_sha256_digest(self.shared_secret, self.send_magic_const)
+        message = rand.random_bytes(padding_length) + magic + self.send_crypto.crypt(self.queued_data)
+        self.queued_data = ''
+
+        log.debug("%s: Transmitting %d bytes (with magic)." % (log_prefix, len(message)))
+        circuit.downstream.write(message)
+
+        self.state = ST_SEARCHING_MAGIC
+
+    def _scan_for_magic(self, data):
+        """
+        Scan 'data' for the magic string. If found, drain it and all
+        the padding before it. Then open the connection.
+        """
+
+        log_prefix = "obfs3:_scan_for_magic()"
+        log.debug("%s: Searching for magic." % log_prefix)
+
+        assert(self.other_magic_value)
+        chunk = data.peek()
+
+        index = chunk.find(self.other_magic_value)
+        if index < 0:
+            if (len(data) > MAX_PADDING):
+                raise base.PluggableTransportError("obfs3: Too much padding (%d)!" % len(data))
+            log.debug("%s: Did not find magic this time (%d)." % (log_prefix, len(data)))
+            return
+
+        index += len(self.other_magic_value)
+        log.debug("%s: Found magic. Draining %d bytes." % (log_prefix, index))
+        data.drain(index)
+
+        self.state = ST_OPEN
+
+    def _derive_crypto(self, pad_string):
+        """
+        Derive and return an obfs3 key using the pad string in 'pad_string'.
+        """
+        secret = hmac_sha256.hmac_sha256_digest(self.shared_secret, pad_string)
+        return aes.AES_CTR_128(secret[:KEYLEN], secret[KEYLEN:])
+
+class Obfs3Client(Obfs3Transport):
+
+    """
+    Obfs3Client is a client for the obfs3 protocol.
+    The client and server differ in terms of their padding strings.
+    """
+
+    def __init__(self):
+        Obfs3Transport.__init__(self)
+
+        self.send_keytype = "Initiator obfuscated data"
+        self.recv_keytype = "Responder obfuscated data"
+        self.send_magic_const = "Initiator magic"
+        self.recv_magic_const = "Responder magic"
+        self.we_are_initiator = True
+
+class Obfs3Server(Obfs3Transport):
+
+    """
+    Obfs3Server is a server for the obfs3 protocol.
+    The client and server differ in terms of their padding strings.
+    """
+
+    def __init__(self):
+        Obfs3Transport.__init__(self)
+
+        self.send_keytype = "Responder obfuscated data"
+        self.recv_keytype = "Initiator obfuscated data"
+        self.send_magic_const = "Responder magic"
+        self.recv_magic_const = "Initiator magic"
+        self.we_are_initiator = False
+
+
+
diff --git a/obfsproxy/transports/transports.py b/obfsproxy/transports/transports.py
index 60fcc52..77aca7c 100644
--- a/obfsproxy/transports/transports.py
+++ b/obfsproxy/transports/transports.py
@@ -2,10 +2,12 @@
 import obfsproxy.transports.dummy as dummy
 import obfsproxy.transports.b64 as b64
 import obfsproxy.transports.obfs2 as obfs2
+import obfsproxy.transports.obfs3 as obfs3
 
 transports = { 'dummy' : {'client' : dummy.DummyClient, 'server' : dummy.DummyServer },
                'b64' : {'client' : b64.B64Client, 'server' : b64.B64Server },
-               'obfs2' : {'client' : obfs2.Obfs2Client, 'server' : obfs2.Obfs2Server } }
+               'obfs2' : {'client' : obfs2.Obfs2Client, 'server' : obfs2.Obfs2Server },
+               'obfs3' : {'client' : obfs3.Obfs3Client, 'server' : obfs3.Obfs3Server } }
 
 def get_transport_class(name, role):
     # Rewrite equivalent roles.

-- 
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